Файловый менеджер - Редактировать - /home/clickysoft/public_html/jmapi5.clickysoft.net/doctrine.tar
Назад
deprecations/composer.json 0000644 00000002311 15021222234 0011734 0 ustar 00 { "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", "phpstan/phpstan": "1.4.10 || 1.10.15", "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "psalm/plugin-phpunit": "0.18.4", "psr/log": "^1 || ^2 || ^3", "vimeo/psalm": "4.30.0 || 5.12.0" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" }, "autoload": { "psr-4": { "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" } }, "autoload-dev": { "psr-4": { "DeprecationTests\\": "test_fixtures/src", "Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php 0000644 00000003663 15021222234 0021767 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Deprecations\PHPUnit; use Doctrine\Deprecations\Deprecation; use function sprintf; trait VerifyDeprecations { /** @var array<string,int> */ private $doctrineDeprecationsExpectations = []; /** @var array<string,int> */ private $doctrineNoDeprecationsExpectations = []; public function expectDeprecationWithIdentifier(string $identifier): void { $this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; } public function expectNoDeprecationWithIdentifier(string $identifier): void { $this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; } /** * @before */ public function enableDeprecationTracking(): void { Deprecation::enableTrackingDeprecations(); } /** * @after */ public function verifyDeprecationsAreTriggered(): void { foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) { $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; $this->assertTrue( $actualCount > $expectation, sprintf( "Expected deprecation with identifier '%s' was not triggered by code executed in test.", $identifier ) ); } foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) { $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; $this->assertTrue( $actualCount === $expectation, sprintf( "Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.", $identifier ) ); } } } deprecations/lib/Doctrine/Deprecations/Deprecation.php 0000644 00000022472 15021222234 0017127 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Deprecations; use Psr\Log\LoggerInterface; use function array_key_exists; use function array_reduce; use function assert; use function debug_backtrace; use function sprintf; use function str_replace; use function strpos; use function strrpos; use function substr; use function trigger_error; use const DEBUG_BACKTRACE_IGNORE_ARGS; use const DIRECTORY_SEPARATOR; use const E_USER_DEPRECATED; /** * Manages Deprecation logging in different ways. * * By default triggered exceptions are not logged. * * To enable different deprecation logging mechanisms you can call the * following methods: * * - Minimal collection of deprecations via getTriggeredDeprecations() * \Doctrine\Deprecations\Deprecation::enableTrackingDeprecations(); * * - Uses @trigger_error with E_USER_DEPRECATED * \Doctrine\Deprecations\Deprecation::enableWithTriggerError(); * * - Sends deprecation messages via a PSR-3 logger * \Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger); * * Packages that trigger deprecations should use the `trigger()` or * `triggerIfCalledFromOutside()` methods. */ class Deprecation { private const TYPE_NONE = 0; private const TYPE_TRACK_DEPRECATIONS = 1; private const TYPE_TRIGGER_ERROR = 2; private const TYPE_PSR_LOGGER = 4; /** @var int-mask-of<self::TYPE_*>|null */ private static $type; /** @var LoggerInterface|null */ private static $logger; /** @var array<string,bool> */ private static $ignoredPackages = []; /** @var array<string,int> */ private static $triggeredDeprecations = []; /** @var array<string,bool> */ private static $ignoredLinks = []; /** @var bool */ private static $deduplication = true; /** * Trigger a deprecation for the given package and identfier. * * The link should point to a Github issue or Wiki entry detailing the * deprecation. It is additionally used to de-duplicate the trigger of the * same deprecation during a request. * * @param float|int|string $args */ public static function trigger(string $package, string $link, string $message, ...$args): void { $type = self::$type ?? self::getTypeFromEnv(); if ($type === self::TYPE_NONE) { return; } if (isset(self::$ignoredLinks[$link])) { return; } if (array_key_exists($link, self::$triggeredDeprecations)) { self::$triggeredDeprecations[$link]++; } else { self::$triggeredDeprecations[$link] = 1; } if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) { return; } if (isset(self::$ignoredPackages[$package])) { return; } $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); $message = sprintf($message, ...$args); self::delegateTriggerToBackend($message, $backtrace, $link, $package); } /** * Trigger a deprecation for the given package and identifier when called from outside. * * "Outside" means we assume that $package is currently installed as a * dependency and the caller is not a file in that package. When $package * is installed as a root package then deprecations triggered from the * tests folder are also considered "outside". * * This deprecation method assumes that you are using Composer to install * the dependency and are using the default /vendor/ folder and not a * Composer plugin to change the install location. The assumption is also * that $package is the exact composer packge name. * * Compared to {@link trigger()} this method causes some overhead when * deprecation tracking is enabled even during deduplication, because it * needs to call {@link debug_backtrace()} * * @param float|int|string $args */ public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void { $type = self::$type ?? self::getTypeFromEnv(); if ($type === self::TYPE_NONE) { return; } $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); // first check that the caller is not from a tests folder, in which case we always let deprecations pass if (isset($backtrace[1]['file'], $backtrace[0]['file']) && strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) { $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $package) . DIRECTORY_SEPARATOR; if (strpos($backtrace[0]['file'], $path) === false) { return; } if (strpos($backtrace[1]['file'], $path) !== false) { return; } } if (isset(self::$ignoredLinks[$link])) { return; } if (array_key_exists($link, self::$triggeredDeprecations)) { self::$triggeredDeprecations[$link]++; } else { self::$triggeredDeprecations[$link] = 1; } if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) { return; } if (isset(self::$ignoredPackages[$package])) { return; } $message = sprintf($message, ...$args); self::delegateTriggerToBackend($message, $backtrace, $link, $package); } /** * @param list<array{function: string, line?: int, file?: string, class?: class-string, type?: string, args?: mixed[], object?: object}> $backtrace */ private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void { $type = self::$type ?? self::getTypeFromEnv(); if (($type & self::TYPE_PSR_LOGGER) > 0) { $context = [ 'file' => $backtrace[0]['file'] ?? null, 'line' => $backtrace[0]['line'] ?? null, 'package' => $package, 'link' => $link, ]; assert(self::$logger !== null); self::$logger->notice($message, $context); } if (! (($type & self::TYPE_TRIGGER_ERROR) > 0)) { return; } $message .= sprintf( ' (%s:%d called by %s:%d, %s, package %s)', self::basename($backtrace[0]['file'] ?? 'native code'), $backtrace[0]['line'] ?? 0, self::basename($backtrace[1]['file'] ?? 'native code'), $backtrace[1]['line'] ?? 0, $link, $package ); @trigger_error($message, E_USER_DEPRECATED); } /** * A non-local-aware version of PHPs basename function. */ private static function basename(string $filename): string { $pos = strrpos($filename, DIRECTORY_SEPARATOR); if ($pos === false) { return $filename; } return substr($filename, $pos + 1); } public static function enableTrackingDeprecations(): void { self::$type = self::$type ?? 0; self::$type |= self::TYPE_TRACK_DEPRECATIONS; } public static function enableWithTriggerError(): void { self::$type = self::$type ?? 0; self::$type |= self::TYPE_TRIGGER_ERROR; } public static function enableWithPsrLogger(LoggerInterface $logger): void { self::$type = self::$type ?? 0; self::$type |= self::TYPE_PSR_LOGGER; self::$logger = $logger; } public static function withoutDeduplication(): void { self::$deduplication = false; } public static function disable(): void { self::$type = self::TYPE_NONE; self::$logger = null; self::$deduplication = true; self::$ignoredLinks = []; foreach (self::$triggeredDeprecations as $link => $count) { self::$triggeredDeprecations[$link] = 0; } } public static function ignorePackage(string $packageName): void { self::$ignoredPackages[$packageName] = true; } public static function ignoreDeprecations(string ...$links): void { foreach ($links as $link) { self::$ignoredLinks[$link] = true; } } public static function getUniqueTriggeredDeprecationsCount(): int { return array_reduce(self::$triggeredDeprecations, static function (int $carry, int $count) { return $carry + $count; }, 0); } /** * Returns each triggered deprecation link identifier and the amount of occurrences. * * @return array<string,int> */ public static function getTriggeredDeprecations(): array { return self::$triggeredDeprecations; } /** * @return int-mask-of<self::TYPE_*> */ private static function getTypeFromEnv(): int { switch ($_SERVER['DOCTRINE_DEPRECATIONS'] ?? $_ENV['DOCTRINE_DEPRECATIONS'] ?? null) { case 'trigger': self::$type = self::TYPE_TRIGGER_ERROR; break; case 'track': self::$type = self::TYPE_TRACK_DEPRECATIONS; break; default: self::$type = self::TYPE_NONE; break; } return self::$type; } } deprecations/README.md 0000644 00000010444 15021222234 0010477 0 ustar 00 # 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(); } } ``` ## 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. deprecations/LICENSE 0000644 00000002051 15021222234 0010220 0 ustar 00 Copyright (c) 2020-2021 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cache/composer.json 0000644 00000003152 15021222234 0010323 0 ustar 00 { "name": "doctrine/cache", "type": "library", "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", "keywords": [ "php", "cache", "caching", "abstraction", "redis", "memcached", "couchdb", "xcache", "apcu" ], "homepage": "https://www.doctrine-project.org/projects/cache.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"}, {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} ], "require": { "php": "~7.1 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "doctrine/coding-standard": "^9", "psr/cache": "^1.0 || ^2.0 || ^3.0", "cache/integration-tests": "dev-master", "symfony/cache": "^4.4 || ^5.4 || ^6", "symfony/var-exporter": "^4.4 || ^5.4 || ^6" }, "conflict": { "doctrine/common": ">2.2,<2.4" }, "autoload": { "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } cache/UPGRADE-1.11.md 0000644 00000002362 15021222234 0007572 0 ustar 00 # Upgrade to 1.11 doctrine/cache will no longer be maintained and all cache implementations have been marked as deprecated. These implementations will be removed in 2.0, which will only contain interfaces to provide a lightweight package for backward compatibility. There are two new classes to use in the `Doctrine\Common\Cache\Psr6` namespace: * The `CacheAdapter` class allows using any Doctrine Cache as PSR-6 cache. This is useful to provide a forward compatibility layer in libraries that accept Doctrine cache implementations and switch to PSR-6. * The `DoctrineProvider` class allows using any PSR-6 cache as Doctrine cache. This implementation is designed for libraries that leak the cache and want to switch to allowing PSR-6 implementations. This class is design to be used during the transition phase of sunsetting doctrine/cache support. A full example to setup a filesystem based PSR-6 cache with symfony/cache using the `DoctrineProvider` to convert back to Doctrine's `Cache` interface: ```php use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Symfony\Component\Cache\Adapter\FilesystemAdapter; $cachePool = new FilesystemAdapter(); $cache = DoctrineProvider::wrap($cachePool); // $cache instanceof \Doctrine\Common\Cache\Cache ``` cache/lib/Doctrine/Common/Cache/Psr6/InvalidArgument.php 0000644 00000000421 15021222234 0017001 0 ustar 00 <?php namespace Doctrine\Common\Cache\Psr6; use InvalidArgumentException; use Psr\Cache\InvalidArgumentException as PsrInvalidArgumentException; /** * @internal */ final class InvalidArgument extends InvalidArgumentException implements PsrInvalidArgumentException { } cache/lib/Doctrine/Common/Cache/Psr6/DoctrineProvider.php 0000644 00000005666 15021222234 0017212 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Doctrine\Common\Cache\Psr6; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\CacheProvider; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\Adapter\DoctrineAdapter as SymfonyDoctrineAdapter; use Symfony\Contracts\Service\ResetInterface; use function rawurlencode; /** * This class was copied from the Symfony Framework, see the original copyright * notice above. The code is distributed subject to the license terms in * https://github.com/symfony/symfony/blob/ff0cf61278982539c49e467db9ab13cbd342f76d/LICENSE */ final class DoctrineProvider extends CacheProvider { /** @var CacheItemPoolInterface */ private $pool; public static function wrap(CacheItemPoolInterface $pool): Cache { if ($pool instanceof CacheAdapter) { return $pool->getCache(); } if ($pool instanceof SymfonyDoctrineAdapter) { $getCache = function () { // phpcs:ignore Squiz.Scope.StaticThisUsage.Found return $this->provider; }; return $getCache->bindTo($pool, SymfonyDoctrineAdapter::class)(); } return new self($pool); } private function __construct(CacheItemPoolInterface $pool) { $this->pool = $pool; } /** @internal */ public function getPool(): CacheItemPoolInterface { return $this->pool; } public function reset(): void { if ($this->pool instanceof ResetInterface) { $this->pool->reset(); } $this->setNamespace($this->getNamespace()); } /** * {@inheritdoc} */ protected function doFetch($id) { $item = $this->pool->getItem(rawurlencode($id)); return $item->isHit() ? $item->get() : false; } /** * {@inheritdoc} * * @return bool */ protected function doContains($id) { return $this->pool->hasItem(rawurlencode($id)); } /** * {@inheritdoc} * * @return bool */ protected function doSave($id, $data, $lifeTime = 0) { $item = $this->pool->getItem(rawurlencode($id)); if (0 < $lifeTime) { $item->expiresAfter($lifeTime); } return $this->pool->save($item->set($data)); } /** * {@inheritdoc} * * @return bool */ protected function doDelete($id) { return $this->pool->deleteItem(rawurlencode($id)); } /** * {@inheritdoc} * * @return bool */ protected function doFlush() { return $this->pool->clear(); } /** * {@inheritdoc} * * @return array|null */ protected function doGetStats() { return null; } } cache/lib/Doctrine/Common/Cache/Psr6/CacheAdapter.php 0000644 00000020032 15021222234 0016214 0 ustar 00 <?php namespace Doctrine\Common\Cache\Psr6; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\ClearableCache; use Doctrine\Common\Cache\MultiDeleteCache; use Doctrine\Common\Cache\MultiGetCache; use Doctrine\Common\Cache\MultiPutCache; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\DoctrineProvider as SymfonyDoctrineProvider; use function array_key_exists; use function assert; use function count; use function current; use function get_class; use function gettype; use function is_object; use function is_string; use function microtime; use function sprintf; use function strpbrk; use const PHP_VERSION_ID; final class CacheAdapter implements CacheItemPoolInterface { private const RESERVED_CHARACTERS = '{}()/\@:'; /** @var Cache */ private $cache; /** @var array<CacheItem|TypedCacheItem> */ private $deferredItems = []; public static function wrap(Cache $cache): CacheItemPoolInterface { if ($cache instanceof DoctrineProvider && ! $cache->getNamespace()) { return $cache->getPool(); } if ($cache instanceof SymfonyDoctrineProvider && ! $cache->getNamespace()) { $getPool = function () { // phpcs:ignore Squiz.Scope.StaticThisUsage.Found return $this->pool; }; return $getPool->bindTo($cache, SymfonyDoctrineProvider::class)(); } return new self($cache); } private function __construct(Cache $cache) { $this->cache = $cache; } /** @internal */ public function getCache(): Cache { return $this->cache; } /** * {@inheritDoc} */ public function getItem($key): CacheItemInterface { assert(self::validKey($key)); if (isset($this->deferredItems[$key])) { $this->commit(); } $value = $this->cache->fetch($key); if (PHP_VERSION_ID >= 80000) { if ($value !== false) { return new TypedCacheItem($key, $value, true); } return new TypedCacheItem($key, null, false); } if ($value !== false) { return new CacheItem($key, $value, true); } return new CacheItem($key, null, false); } /** * {@inheritDoc} */ public function getItems(array $keys = []): array { if ($this->deferredItems) { $this->commit(); } assert(self::validKeys($keys)); $values = $this->doFetchMultiple($keys); $items = []; if (PHP_VERSION_ID >= 80000) { foreach ($keys as $key) { if (array_key_exists($key, $values)) { $items[$key] = new TypedCacheItem($key, $values[$key], true); } else { $items[$key] = new TypedCacheItem($key, null, false); } } return $items; } foreach ($keys as $key) { if (array_key_exists($key, $values)) { $items[$key] = new CacheItem($key, $values[$key], true); } else { $items[$key] = new CacheItem($key, null, false); } } return $items; } /** * {@inheritDoc} */ public function hasItem($key): bool { assert(self::validKey($key)); if (isset($this->deferredItems[$key])) { $this->commit(); } return $this->cache->contains($key); } public function clear(): bool { $this->deferredItems = []; if (! $this->cache instanceof ClearableCache) { return false; } return $this->cache->deleteAll(); } /** * {@inheritDoc} */ public function deleteItem($key): bool { assert(self::validKey($key)); unset($this->deferredItems[$key]); return $this->cache->delete($key); } /** * {@inheritDoc} */ public function deleteItems(array $keys): bool { foreach ($keys as $key) { assert(self::validKey($key)); unset($this->deferredItems[$key]); } return $this->doDeleteMultiple($keys); } public function save(CacheItemInterface $item): bool { return $this->saveDeferred($item) && $this->commit(); } public function saveDeferred(CacheItemInterface $item): bool { if (! $item instanceof CacheItem && ! $item instanceof TypedCacheItem) { return false; } $this->deferredItems[$item->getKey()] = $item; return true; } public function commit(): bool { if (! $this->deferredItems) { return true; } $now = microtime(true); $itemsCount = 0; $byLifetime = []; $expiredKeys = []; foreach ($this->deferredItems as $key => $item) { $lifetime = ($item->getExpiry() ?? $now) - $now; if ($lifetime < 0) { $expiredKeys[] = $key; continue; } ++$itemsCount; $byLifetime[(int) $lifetime][$key] = $item->get(); } $this->deferredItems = []; switch (count($expiredKeys)) { case 0: break; case 1: $this->cache->delete(current($expiredKeys)); break; default: $this->doDeleteMultiple($expiredKeys); break; } if ($itemsCount === 1) { return $this->cache->save($key, $item->get(), (int) $lifetime); } $success = true; foreach ($byLifetime as $lifetime => $values) { $success = $this->doSaveMultiple($values, $lifetime) && $success; } return $success; } public function __destruct() { $this->commit(); } /** * @param mixed $key */ private static function validKey($key): bool { if (! is_string($key)) { throw new InvalidArgument(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } if ($key === '') { throw new InvalidArgument('Cache key length must be greater than zero.'); } if (strpbrk($key, self::RESERVED_CHARACTERS) !== false) { throw new InvalidArgument(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS)); } return true; } /** * @param mixed[] $keys */ private static function validKeys(array $keys): bool { foreach ($keys as $key) { self::validKey($key); } return true; } /** * @param mixed[] $keys */ private function doDeleteMultiple(array $keys): bool { if ($this->cache instanceof MultiDeleteCache) { return $this->cache->deleteMultiple($keys); } $success = true; foreach ($keys as $key) { $success = $this->cache->delete($key) && $success; } return $success; } /** * @param mixed[] $keys * * @return mixed[] */ private function doFetchMultiple(array $keys): array { if ($this->cache instanceof MultiGetCache) { return $this->cache->fetchMultiple($keys); } $values = []; foreach ($keys as $key) { $value = $this->cache->fetch($key); if (! $value) { continue; } $values[$key] = $value; } return $values; } /** * @param mixed[] $keysAndValues */ private function doSaveMultiple(array $keysAndValues, int $lifetime = 0): bool { if ($this->cache instanceof MultiPutCache) { return $this->cache->saveMultiple($keysAndValues, $lifetime); } $success = true; foreach ($keysAndValues as $key => $value) { $success = $this->cache->save($key, $value, $lifetime) && $success; } return $success; } } cache/lib/Doctrine/Common/Cache/Psr6/CacheItem.php 0000644 00000005006 15021222234 0015536 0 ustar 00 <?php namespace Doctrine\Common\Cache\Psr6; use DateInterval; use DateTime; use DateTimeInterface; use Psr\Cache\CacheItemInterface; use TypeError; use function get_class; use function gettype; use function is_int; use function is_object; use function microtime; use function sprintf; final class CacheItem implements CacheItemInterface { /** @var string */ private $key; /** @var mixed */ private $value; /** @var bool */ private $isHit; /** @var float|null */ private $expiry; /** * @internal * * @param mixed $data */ public function __construct(string $key, $data, bool $isHit) { $this->key = $key; $this->value = $data; $this->isHit = $isHit; } public function getKey(): string { return $this->key; } /** * {@inheritDoc} * * @return mixed */ public function get() { return $this->value; } public function isHit(): bool { return $this->isHit; } /** * {@inheritDoc} */ public function set($value): self { $this->value = $value; return $this; } /** * {@inheritDoc} */ public function expiresAt($expiration): self { if ($expiration === null) { $this->expiry = null; } elseif ($expiration instanceof DateTimeInterface) { $this->expiry = (float) $expiration->format('U.u'); } else { throw new TypeError(sprintf( 'Expected $expiration to be an instance of DateTimeInterface or null, got %s', is_object($expiration) ? get_class($expiration) : gettype($expiration) )); } return $this; } /** * {@inheritDoc} */ public function expiresAfter($time): self { if ($time === null) { $this->expiry = null; } elseif ($time instanceof DateInterval) { $this->expiry = microtime(true) + DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); } elseif (is_int($time)) { $this->expiry = $time + microtime(true); } else { throw new TypeError(sprintf( 'Expected $time to be either an integer, an instance of DateInterval or null, got %s', is_object($time) ? get_class($time) : gettype($time) )); } return $this; } /** * @internal */ public function getExpiry(): ?float { return $this->expiry; } } cache/lib/Doctrine/Common/Cache/Psr6/TypedCacheItem.php 0000644 00000004173 15021222234 0016550 0 ustar 00 <?php namespace Doctrine\Common\Cache\Psr6; use DateInterval; use DateTime; use DateTimeInterface; use Psr\Cache\CacheItemInterface; use TypeError; use function get_debug_type; use function is_int; use function microtime; use function sprintf; final class TypedCacheItem implements CacheItemInterface { private ?float $expiry = null; /** * @internal */ public function __construct( private string $key, private mixed $value, private bool $isHit, ) { } public function getKey(): string { return $this->key; } public function get(): mixed { return $this->value; } public function isHit(): bool { return $this->isHit; } public function set(mixed $value): static { $this->value = $value; return $this; } /** * {@inheritDoc} */ public function expiresAt($expiration): static { if ($expiration === null) { $this->expiry = null; } elseif ($expiration instanceof DateTimeInterface) { $this->expiry = (float) $expiration->format('U.u'); } else { throw new TypeError(sprintf( 'Expected $expiration to be an instance of DateTimeInterface or null, got %s', get_debug_type($expiration) )); } return $this; } /** * {@inheritDoc} */ public function expiresAfter($time): static { if ($time === null) { $this->expiry = null; } elseif ($time instanceof DateInterval) { $this->expiry = microtime(true) + DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); } elseif (is_int($time)) { $this->expiry = $time + microtime(true); } else { throw new TypeError(sprintf( 'Expected $time to be either an integer, an instance of DateInterval or null, got %s', get_debug_type($time) )); } return $this; } /** * @internal */ public function getExpiry(): ?float { return $this->expiry; } } cache/lib/Doctrine/Common/Cache/ClearableCache.php 0000644 00000000765 15021222234 0015667 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache that can be flushed. * * Intended to be used for partial clearing of a cache namespace. For a more * global "flushing", see {@see FlushableCache}. * * @link www.doctrine-project.org */ interface ClearableCache { /** * Deletes all cache entries in the current cache namespace. * * @return bool TRUE if the cache entries were successfully deleted, FALSE otherwise. */ public function deleteAll(); } cache/lib/Doctrine/Common/Cache/Cache.php 0000644 00000005357 15021222234 0014076 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache drivers. * * @link www.doctrine-project.org */ interface Cache { public const STATS_HITS = 'hits'; public const STATS_MISSES = 'misses'; public const STATS_UPTIME = 'uptime'; public const STATS_MEMORY_USAGE = 'memory_usage'; public const STATS_MEMORY_AVAILABLE = 'memory_available'; /** * Only for backward compatibility (may be removed in next major release) * * @deprecated */ public const STATS_MEMORY_AVAILIABLE = 'memory_available'; /** * Fetches an entry from the cache. * * @param string $id The id of the cache entry to fetch. * * @return mixed The cached data or FALSE, if no cache entry exists for the given id. */ public function fetch($id); /** * Tests if an entry exists in the cache. * * @param string $id The cache id of the entry to check for. * * @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise. */ public function contains($id); /** * Puts data into the cache. * * If a cache entry with the given id already exists, its data will be replaced. * * @param string $id The cache id. * @param mixed $data The cache entry/data. * @param int $lifeTime The lifetime in number of seconds for this cache entry. * If zero (the default), the entry never expires (although it may be deleted from the cache * to make place for other entries). * * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise. */ public function save($id, $data, $lifeTime = 0); /** * Deletes a cache entry. * * @param string $id The cache id. * * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise. * Deleting a non-existing entry is considered successful. */ public function delete($id); /** * Retrieves cached information from the data store. * * The server's statistics array has the following values: * * - <b>hits</b> * Number of keys that have been requested and found present. * * - <b>misses</b> * Number of items that have been requested and not found. * * - <b>uptime</b> * Time that the server is running. * * - <b>memory_usage</b> * Memory used by this server to store items. * * - <b>memory_available</b> * Memory allowed to use for storage. * * @return mixed[]|null An associative array with server's statistics if available, NULL otherwise. */ public function getStats(); } cache/lib/Doctrine/Common/Cache/CacheProvider.php 0000644 00000020433 15021222234 0015601 0 ustar 00 <?php namespace Doctrine\Common\Cache; use function array_combine; use function array_key_exists; use function array_map; use function sprintf; /** * Base class for cache provider implementations. */ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, MultiOperationCache { public const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]'; /** * The namespace to prefix all cache ids with. * * @var string */ private $namespace = ''; /** * The namespace version. * * @var int|null */ private $namespaceVersion; /** * Sets the namespace to prefix all cache ids with. * * @param string $namespace * * @return void */ public function setNamespace($namespace) { $this->namespace = (string) $namespace; $this->namespaceVersion = null; } /** * Retrieves the namespace that prefixes all cache ids. * * @return string */ public function getNamespace() { return $this->namespace; } /** * {@inheritdoc} */ public function fetch($id) { return $this->doFetch($this->getNamespacedId($id)); } /** * {@inheritdoc} */ public function fetchMultiple(array $keys) { if (empty($keys)) { return []; } // note: the array_combine() is in place to keep an association between our $keys and the $namespacedKeys $namespacedKeys = array_combine($keys, array_map([$this, 'getNamespacedId'], $keys)); $items = $this->doFetchMultiple($namespacedKeys); $foundItems = []; // no internal array function supports this sort of mapping: needs to be iterative // this filters and combines keys in one pass foreach ($namespacedKeys as $requestedKey => $namespacedKey) { if (! isset($items[$namespacedKey]) && ! array_key_exists($namespacedKey, $items)) { continue; } $foundItems[$requestedKey] = $items[$namespacedKey]; } return $foundItems; } /** * {@inheritdoc} */ public function saveMultiple(array $keysAndValues, $lifetime = 0) { $namespacedKeysAndValues = []; foreach ($keysAndValues as $key => $value) { $namespacedKeysAndValues[$this->getNamespacedId($key)] = $value; } return $this->doSaveMultiple($namespacedKeysAndValues, $lifetime); } /** * {@inheritdoc} */ public function contains($id) { return $this->doContains($this->getNamespacedId($id)); } /** * {@inheritdoc} */ public function save($id, $data, $lifeTime = 0) { return $this->doSave($this->getNamespacedId($id), $data, $lifeTime); } /** * {@inheritdoc} */ public function deleteMultiple(array $keys) { return $this->doDeleteMultiple(array_map([$this, 'getNamespacedId'], $keys)); } /** * {@inheritdoc} */ public function delete($id) { return $this->doDelete($this->getNamespacedId($id)); } /** * {@inheritdoc} */ public function getStats() { return $this->doGetStats(); } /** * {@inheritDoc} */ public function flushAll() { return $this->doFlush(); } /** * {@inheritDoc} */ public function deleteAll() { $namespaceCacheKey = $this->getNamespaceCacheKey(); $namespaceVersion = $this->getNamespaceVersion() + 1; if ($this->doSave($namespaceCacheKey, $namespaceVersion)) { $this->namespaceVersion = $namespaceVersion; return true; } return false; } /** * Prefixes the passed id with the configured namespace value. * * @param string $id The id to namespace. * * @return string The namespaced id. */ private function getNamespacedId(string $id): string { $namespaceVersion = $this->getNamespaceVersion(); return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion); } /** * Returns the namespace cache key. */ private function getNamespaceCacheKey(): string { return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace); } /** * Returns the namespace version. */ private function getNamespaceVersion(): int { if ($this->namespaceVersion !== null) { return $this->namespaceVersion; } $namespaceCacheKey = $this->getNamespaceCacheKey(); $this->namespaceVersion = (int) $this->doFetch($namespaceCacheKey) ?: 1; return $this->namespaceVersion; } /** * Default implementation of doFetchMultiple. Each driver that supports multi-get should owerwrite it. * * @param string[] $keys Array of keys to retrieve from cache * * @return mixed[] Array of values retrieved for the given keys. */ protected function doFetchMultiple(array $keys) { $returnValues = []; foreach ($keys as $key) { $item = $this->doFetch($key); if ($item === false && ! $this->doContains($key)) { continue; } $returnValues[$key] = $item; } return $returnValues; } /** * Fetches an entry from the cache. * * @param string $id The id of the cache entry to fetch. * * @return mixed|false The cached data or FALSE, if no cache entry exists for the given id. */ abstract protected function doFetch($id); /** * Tests if an entry exists in the cache. * * @param string $id The cache id of the entry to check for. * * @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise. */ abstract protected function doContains($id); /** * Default implementation of doSaveMultiple. Each driver that supports multi-put should override it. * * @param mixed[] $keysAndValues Array of keys and values to save in cache * @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these * cache entries (0 => infinite lifeTime). * * @return bool TRUE if the operation was successful, FALSE if it wasn't. */ protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) { $success = true; foreach ($keysAndValues as $key => $value) { if ($this->doSave($key, $value, $lifetime)) { continue; } $success = false; } return $success; } /** * Puts data into the cache. * * @param string $id The cache id. * @param string $data The cache entry/data. * @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this * cache entry (0 => infinite lifeTime). * * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise. */ abstract protected function doSave($id, $data, $lifeTime = 0); /** * Default implementation of doDeleteMultiple. Each driver that supports multi-delete should override it. * * @param string[] $keys Array of keys to delete from cache * * @return bool TRUE if the operation was successful, FALSE if it wasn't */ protected function doDeleteMultiple(array $keys) { $success = true; foreach ($keys as $key) { if ($this->doDelete($key)) { continue; } $success = false; } return $success; } /** * Deletes a cache entry. * * @param string $id The cache id. * * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise. */ abstract protected function doDelete($id); /** * Flushes all cache entries. * * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise. */ abstract protected function doFlush(); /** * Retrieves cached information from the data store. * * @return mixed[]|null An associative array with server's statistics if available, NULL otherwise. */ abstract protected function doGetStats(); } cache/lib/Doctrine/Common/Cache/FlushableCache.php 0000644 00000000536 15021222234 0015716 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache that can be flushed. * * @link www.doctrine-project.org */ interface FlushableCache { /** * Flushes all cache entries, globally. * * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise. */ public function flushAll(); } cache/lib/Doctrine/Common/Cache/MultiOperationCache.php 0000644 00000000373 15021222234 0016763 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache drivers that supports multiple items manipulation. * * @link www.doctrine-project.org */ interface MultiOperationCache extends MultiGetCache, MultiDeleteCache, MultiPutCache { } cache/lib/Doctrine/Common/Cache/MultiPutCache.php 0000644 00000001300 15021222234 0015562 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache drivers that allows to put many items at once. * * @deprecated * * @link www.doctrine-project.org */ interface MultiPutCache { /** * Returns a boolean value indicating if the operation succeeded. * * @param mixed[] $keysAndValues Array of keys and values to save in cache * @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these * cache entries (0 => infinite lifeTime). * * @return bool TRUE if the operation was successful, FALSE if it wasn't. */ public function saveMultiple(array $keysAndValues, $lifetime = 0); } cache/lib/Doctrine/Common/Cache/MultiDeleteCache.php 0000644 00000000722 15021222234 0016223 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache drivers that allows to put many items at once. * * @deprecated * * @link www.doctrine-project.org */ interface MultiDeleteCache { /** * Deletes several cache entries. * * @param string[] $keys Array of keys to delete from cache * * @return bool TRUE if the operation was successful, FALSE if it wasn't. */ public function deleteMultiple(array $keys); } cache/lib/Doctrine/Common/Cache/MultiGetCache.php 0000644 00000001121 15021222234 0015532 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache drivers that allows to get many items at once. * * @deprecated * * @link www.doctrine-project.org */ interface MultiGetCache { /** * Returns an associative array of values for keys is found in cache. * * @param string[] $keys Array of keys to retrieve from cache * * @return mixed[] Array of retrieved values, indexed by the specified keys. * Values that couldn't be retrieved are not contained in this array. */ public function fetchMultiple(array $keys); } cache/UPGRADE-1.4.md 0000644 00000001326 15021222234 0007513 0 ustar 00 # Upgrade to 1.4 ## Minor BC Break: `Doctrine\Common\Cache\FileCache#$extension` is now `private`. If you need to override the value of `Doctrine\Common\Cache\FileCache#$extension`, then use the second parameter of `Doctrine\Common\Cache\FileCache#__construct()` instead of overriding the property in your own implementation. ## Minor BC Break: file based caches paths changed `Doctrine\Common\Cache\FileCache`, `Doctrine\Common\Cache\PhpFileCache` and `Doctrine\Common\Cache\FilesystemCache` are using a different cache paths structure. If you rely on warmed up caches for deployments, consider that caches generated with `doctrine/cache` `<1.4` are not compatible with the new directory structure, and will be ignored. cache/README.md 0000644 00000001617 15021222234 0007064 0 ustar 00 # Doctrine Cache [](https://github.com/doctrine/cache/actions) [](https://codecov.io/gh/doctrine/cache/branch/1.10.x) [](https://packagist.org/packages/doctrine/cache) [](https://packagist.org/packages/doctrine/cache) Cache component extracted from the Doctrine Common project. [Documentation](https://www.doctrine-project.org/projects/doctrine-cache/en/current/index.html) This library is deprecated and will no longer receive bug fixes from the Doctrine Project. Please use a different cache library, preferably PSR-6 or PSR-16 instead. cache/LICENSE 0000644 00000002051 15021222234 0006603 0 ustar 00 Copyright (c) 2006-2015 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. inflector/composer.json 0000644 00000003020 15021222234 0011237 0 ustar 00 { "name": "doctrine/inflector", "type": "library", "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", "keywords": ["php", "strings", "words", "manipulation", "inflector", "inflection", "uppercase", "lowercase", "singular", "plural"], "homepage": "https://www.doctrine-project.org/projects/inflector.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"}, {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} ], "require": { "php": "^7.2 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^11.0", "phpstan/phpstan": "^1.8", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.3", "phpunit/phpunit": "^8.5 || ^9.5", "vimeo/psalm": "^4.25 || ^5.4" }, "autoload": { "psr-4": { "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\Inflector\\": "tests/Doctrine/Tests/Inflector" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } inflector/docs/en/index.rst 0000644 00000014574 15021222234 0011730 0 ustar 00 Introduction ============ The Doctrine Inflector has methods for inflecting text. The features include pluralization, singularization, converting between camelCase and under_score and capitalizing words. Installation ============ You can install the Inflector with composer: .. code-block:: console $ composer require doctrine/inflector Usage ===== Using the inflector is easy, you can create a new ``Doctrine\Inflector\Inflector`` instance by using the ``Doctrine\Inflector\InflectorFactory`` class: .. code-block:: php use Doctrine\Inflector\InflectorFactory; $inflector = InflectorFactory::create()->build(); By default it will create an English inflector. If you want to use another language, just pass the language you want to create an inflector for to the ``createForLanguage()`` method: .. code-block:: php use Doctrine\Inflector\InflectorFactory; use Doctrine\Inflector\Language; $inflector = InflectorFactory::createForLanguage(Language::SPANISH)->build(); The supported languages are as follows: - ``Language::ENGLISH`` - ``Language::FRENCH`` - ``Language::NORWEGIAN_BOKMAL`` - ``Language::PORTUGUESE`` - ``Language::SPANISH`` - ``Language::TURKISH`` If you want to manually construct the inflector instead of using a factory, you can do so like this: .. code-block:: php use Doctrine\Inflector\CachedWordInflector; use Doctrine\Inflector\RulesetInflector; use Doctrine\Inflector\Rules\English; $inflector = new Inflector( new CachedWordInflector(new RulesetInflector( English\Rules::getSingularRuleset() )), new CachedWordInflector(new RulesetInflector( English\Rules::getPluralRuleset() )) ); Adding Languages ---------------- If you are interested in adding support for your language, take a look at the other languages defined in the ``Doctrine\Inflector\Rules`` namespace and the tests located in ``Doctrine\Tests\Inflector\Rules``. You can copy one of the languages and update the rules for your language. Once you have done this, send a pull request to the ``doctrine/inflector`` repository with the additions. Custom Setup ============ If you want to setup custom singular and plural rules, you can configure these in the factory: .. code-block:: php use Doctrine\Inflector\InflectorFactory; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Transformations; use Doctrine\Inflector\Rules\Word; $inflector = InflectorFactory::create() ->withSingularRules( new Ruleset( new Transformations( new Transformation(new Pattern('/^(bil)er$/i'), '\1'), new Transformation(new Pattern('/^(inflec|contribu)tors$/i'), '\1ta') ), new Patterns(new Pattern('singulars')), new Substitutions(new Substitution(new Word('spins'), new Word('spinor'))) ) ) ->withPluralRules( new Ruleset( new Transformations( new Transformation(new Pattern('^(bil)er$'), '\1'), new Transformation(new Pattern('^(inflec|contribu)tors$'), '\1ta') ), new Patterns(new Pattern('noflect'), new Pattern('abtuse')), new Substitutions( new Substitution(new Word('amaze'), new Word('amazable')), new Substitution(new Word('phone'), new Word('phonezes')) ) ) ) ->build(); No operation inflector ---------------------- The ``Doctrine\Inflector\NoopWordInflector`` may be used to configure an inflector that doesn't perform any operation for pluralization and/or singularization. If will simply return the input as output. This is an implementation of the `Null Object design pattern <https://sourcemaking.com/design_patterns/null_object>`_. .. code-block:: php use Doctrine\Inflector\Inflector; use Doctrine\Inflector\NoopWordInflector; $inflector = new Inflector(new NoopWordInflector(), new NoopWordInflector()); Tableize ======== Converts ``ModelName`` to ``model_name``: .. code-block:: php echo $inflector->tableize('ModelName'); // model_name Classify ======== Converts ``model_name`` to ``ModelName``: .. code-block:: php echo $inflector->classify('model_name'); // ModelName Camelize ======== This method uses `Classify`_ and then converts the first character to lowercase: .. code-block:: php echo $inflector->camelize('model_name'); // modelName Capitalize ========== Takes a string and capitalizes all of the words, like PHP's built-in ``ucwords`` function. This extends that behavior, however, by allowing the word delimiters to be configured, rather than only separating on whitespace. Here is an example: .. code-block:: php $string = 'top-o-the-morning to all_of_you!'; echo $inflector->capitalize($string); // Top-O-The-Morning To All_of_you! echo $inflector->capitalize($string, '-_ '); // Top-O-The-Morning To All_Of_You! Pluralize ========= Returns a word in plural form. .. code-block:: php echo $inflector->pluralize('browser'); // browsers Singularize =========== Returns a word in singular form. .. code-block:: php echo $inflector->singularize('browsers'); // browser Urlize ====== Generate a URL friendly string from a string of text: .. code-block:: php echo $inflector->urlize('My first blog post'); // my-first-blog-post Unaccent ======== You can unaccent a string of text using the ``unaccent()`` method: .. code-block:: php echo $inflector->unaccent('año'); // ano Legacy API ========== The API present in Inflector 1.x is still available, but will be deprecated in a future release and dropped for 3.0. Support for languages other than English is available in the 2.0 API only. Acknowledgements ================ The language rules in this library have been adapted from several different sources, including but not limited to: - `Ruby On Rails Inflector <http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html>`_ - `ICanBoogie Inflector <https://github.com/ICanBoogie/Inflector>`_ - `CakePHP Inflector <https://book.cakephp.org/3.0/en/core-libraries/inflector.html>`_ inflector/lib/Doctrine/Inflector/NoopWordInflector.php 0000644 00000000311 15021222234 0017105 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; class NoopWordInflector implements WordInflector { public function inflect(string $word): string { return $word; } } inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php 0000644 00000003213 15021222234 0021712 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; use Doctrine\Inflector\Rules\Ruleset; use function array_unshift; abstract class GenericLanguageInflectorFactory implements LanguageInflectorFactory { /** @var Ruleset[] */ private $singularRulesets = []; /** @var Ruleset[] */ private $pluralRulesets = []; final public function __construct() { $this->singularRulesets[] = $this->getSingularRuleset(); $this->pluralRulesets[] = $this->getPluralRuleset(); } final public function build(): Inflector { return new Inflector( new CachedWordInflector(new RulesetInflector( ...$this->singularRulesets )), new CachedWordInflector(new RulesetInflector( ...$this->pluralRulesets )) ); } final public function withSingularRules(?Ruleset $singularRules, bool $reset = false): LanguageInflectorFactory { if ($reset) { $this->singularRulesets = []; } if ($singularRules instanceof Ruleset) { array_unshift($this->singularRulesets, $singularRules); } return $this; } final public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): LanguageInflectorFactory { if ($reset) { $this->pluralRulesets = []; } if ($pluralRules instanceof Ruleset) { array_unshift($this->pluralRulesets, $pluralRules); } return $this; } abstract protected function getSingularRuleset(): Ruleset; abstract protected function getPluralRuleset(): Ruleset; } inflector/lib/Doctrine/Inflector/RulesetInflector.php 0000644 00000002513 15021222234 0016767 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; use Doctrine\Inflector\Rules\Ruleset; use function array_merge; /** * Inflects based on multiple rulesets. * * Rules: * - If the word matches any uninflected word pattern, it is not inflected * - The first ruleset that returns a different value for an irregular word wins * - The first ruleset that returns a different value for a regular word wins * - If none of the above match, the word is left as-is */ class RulesetInflector implements WordInflector { /** @var Ruleset[] */ private $rulesets; public function __construct(Ruleset $ruleset, Ruleset ...$rulesets) { $this->rulesets = array_merge([$ruleset], $rulesets); } public function inflect(string $word): string { if ($word === '') { return ''; } foreach ($this->rulesets as $ruleset) { if ($ruleset->getUninflected()->matches($word)) { return $word; } $inflected = $ruleset->getIrregular()->inflect($word); if ($inflected !== $word) { return $inflected; } $inflected = $ruleset->getRegular()->inflect($word); if ($inflected !== $word) { return $inflected; } } return $word; } } inflector/lib/Doctrine/Inflector/Inflector.php 0000644 00000031060 15021222234 0015422 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; use RuntimeException; use function chr; use function function_exists; use function lcfirst; use function mb_strtolower; use function ord; use function preg_match; use function preg_replace; use function sprintf; use function str_replace; use function strlen; use function strtolower; use function strtr; use function trim; use function ucwords; class Inflector { private const ACCENTED_CHARACTERS = [ 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'Ae', 'Æ' => 'Ae', 'Å' => 'Aa', 'æ' => 'a', 'Ç' => 'C', 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'Oe', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'Ue', 'Ý' => 'Y', 'ß' => 'ss', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'ae', 'å' => 'aa', 'ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'oe', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'ue', 'ý' => 'y', 'ÿ' => 'y', 'Ā' => 'A', 'ā' => 'a', 'Ă' => 'A', 'ă' => 'a', 'Ą' => 'A', 'ą' => 'a', 'Ć' => 'C', 'ć' => 'c', 'Ĉ' => 'C', 'ĉ' => 'c', 'Ċ' => 'C', 'ċ' => 'c', 'Č' => 'C', 'č' => 'c', 'Ď' => 'D', 'ď' => 'd', 'Đ' => 'D', 'đ' => 'd', 'Ē' => 'E', 'ē' => 'e', 'Ĕ' => 'E', 'ĕ' => 'e', 'Ė' => 'E', 'ė' => 'e', 'Ę' => 'E', 'ę' => 'e', 'Ě' => 'E', 'ě' => 'e', 'Ĝ' => 'G', 'ĝ' => 'g', 'Ğ' => 'G', 'ğ' => 'g', 'Ġ' => 'G', 'ġ' => 'g', 'Ģ' => 'G', 'ģ' => 'g', 'Ĥ' => 'H', 'ĥ' => 'h', 'Ħ' => 'H', 'ħ' => 'h', 'Ĩ' => 'I', 'ĩ' => 'i', 'Ī' => 'I', 'ī' => 'i', 'Ĭ' => 'I', 'ĭ' => 'i', 'Į' => 'I', 'į' => 'i', 'İ' => 'I', 'ı' => 'i', 'IJ' => 'IJ', 'ij' => 'ij', 'Ĵ' => 'J', 'ĵ' => 'j', 'Ķ' => 'K', 'ķ' => 'k', 'ĸ' => 'k', 'Ĺ' => 'L', 'ĺ' => 'l', 'Ļ' => 'L', 'ļ' => 'l', 'Ľ' => 'L', 'ľ' => 'l', 'Ŀ' => 'L', 'ŀ' => 'l', 'Ł' => 'L', 'ł' => 'l', 'Ń' => 'N', 'ń' => 'n', 'Ņ' => 'N', 'ņ' => 'n', 'Ň' => 'N', 'ň' => 'n', 'ʼn' => 'N', 'Ŋ' => 'n', 'ŋ' => 'N', 'Ō' => 'O', 'ō' => 'o', 'Ŏ' => 'O', 'ŏ' => 'o', 'Ő' => 'O', 'ő' => 'o', 'Œ' => 'OE', 'œ' => 'oe', 'Ø' => 'O', 'ø' => 'o', 'Ŕ' => 'R', 'ŕ' => 'r', 'Ŗ' => 'R', 'ŗ' => 'r', 'Ř' => 'R', 'ř' => 'r', 'Ś' => 'S', 'ś' => 's', 'Ŝ' => 'S', 'ŝ' => 's', 'Ş' => 'S', 'ş' => 's', 'Š' => 'S', 'š' => 's', 'Ţ' => 'T', 'ţ' => 't', 'Ť' => 'T', 'ť' => 't', 'Ŧ' => 'T', 'ŧ' => 't', 'Ũ' => 'U', 'ũ' => 'u', 'Ū' => 'U', 'ū' => 'u', 'Ŭ' => 'U', 'ŭ' => 'u', 'Ů' => 'U', 'ů' => 'u', 'Ű' => 'U', 'ű' => 'u', 'Ų' => 'U', 'ų' => 'u', 'Ŵ' => 'W', 'ŵ' => 'w', 'Ŷ' => 'Y', 'ŷ' => 'y', 'Ÿ' => 'Y', 'Ź' => 'Z', 'ź' => 'z', 'Ż' => 'Z', 'ż' => 'z', 'Ž' => 'Z', 'ž' => 'z', 'ſ' => 's', '€' => 'E', '£' => '', ]; /** @var WordInflector */ private $singularizer; /** @var WordInflector */ private $pluralizer; public function __construct(WordInflector $singularizer, WordInflector $pluralizer) { $this->singularizer = $singularizer; $this->pluralizer = $pluralizer; } /** * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. */ public function tableize(string $word): string { $tableized = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $word); if ($tableized === null) { throw new RuntimeException(sprintf( 'preg_replace returned null for value "%s"', $word )); } return mb_strtolower($tableized); } /** * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. */ public function classify(string $word): string { return str_replace([' ', '_', '-'], '', ucwords($word, ' _-')); } /** * Camelizes a word. This uses the classify() method and turns the first character to lowercase. */ public function camelize(string $word): string { return lcfirst($this->classify($word)); } /** * Uppercases words with configurable delimiters between words. * * Takes a string and capitalizes all of the words, like PHP's built-in * ucwords function. This extends that behavior, however, by allowing the * word delimiters to be configured, rather than only separating on * whitespace. * * Here is an example: * <code> * <?php * $string = 'top-o-the-morning to all_of_you!'; * echo $inflector->capitalize($string); * // Top-O-The-Morning To All_of_you! * * echo $inflector->capitalize($string, '-_ '); * // Top-O-The-Morning To All_Of_You! * ?> * </code> * * @param string $string The string to operate on. * @param string $delimiters A list of word separators. * * @return string The string with all delimiter-separated words capitalized. */ public function capitalize(string $string, string $delimiters = " \n\t\r\0\x0B-"): string { return ucwords($string, $delimiters); } /** * Checks if the given string seems like it has utf8 characters in it. * * @param string $string The string to check for utf8 characters in. */ public function seemsUtf8(string $string): bool { for ($i = 0; $i < strlen($string); $i++) { if (ord($string[$i]) < 0x80) { continue; // 0bbbbbbb } if ((ord($string[$i]) & 0xE0) === 0xC0) { $n = 1; // 110bbbbb } elseif ((ord($string[$i]) & 0xF0) === 0xE0) { $n = 2; // 1110bbbb } elseif ((ord($string[$i]) & 0xF8) === 0xF0) { $n = 3; // 11110bbb } elseif ((ord($string[$i]) & 0xFC) === 0xF8) { $n = 4; // 111110bb } elseif ((ord($string[$i]) & 0xFE) === 0xFC) { $n = 5; // 1111110b } else { return false; // Does not match any model } for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ? if (++$i === strlen($string) || ((ord($string[$i]) & 0xC0) !== 0x80)) { return false; } } } return true; } /** * Remove any illegal characters, accents, etc. * * @param string $string String to unaccent * * @return string Unaccented string */ public function unaccent(string $string): string { if (preg_match('/[\x80-\xff]/', $string) === false) { return $string; } if ($this->seemsUtf8($string)) { $string = strtr($string, self::ACCENTED_CHARACTERS); } else { $characters = []; // Assume ISO-8859-1 if not UTF-8 $characters['in'] = chr(128) . chr(131) . chr(138) . chr(142) . chr(154) . chr(158) . chr(159) . chr(162) . chr(165) . chr(181) . chr(192) . chr(193) . chr(194) . chr(195) . chr(196) . chr(197) . chr(199) . chr(200) . chr(201) . chr(202) . chr(203) . chr(204) . chr(205) . chr(206) . chr(207) . chr(209) . chr(210) . chr(211) . chr(212) . chr(213) . chr(214) . chr(216) . chr(217) . chr(218) . chr(219) . chr(220) . chr(221) . chr(224) . chr(225) . chr(226) . chr(227) . chr(228) . chr(229) . chr(231) . chr(232) . chr(233) . chr(234) . chr(235) . chr(236) . chr(237) . chr(238) . chr(239) . chr(241) . chr(242) . chr(243) . chr(244) . chr(245) . chr(246) . chr(248) . chr(249) . chr(250) . chr(251) . chr(252) . chr(253) . chr(255); $characters['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'; $string = strtr($string, $characters['in'], $characters['out']); $doubleChars = []; $doubleChars['in'] = [ chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254), ]; $doubleChars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th']; $string = str_replace($doubleChars['in'], $doubleChars['out'], $string); } return $string; } /** * Convert any passed string to a url friendly string. * Converts 'My first blog post' to 'my-first-blog-post' * * @param string $string String to urlize. * * @return string Urlized string. */ public function urlize(string $string): string { // Remove all non url friendly characters with the unaccent function $unaccented = $this->unaccent($string); if (function_exists('mb_strtolower')) { $lowered = mb_strtolower($unaccented); } else { $lowered = strtolower($unaccented); } $replacements = [ '/\W/' => ' ', '/([A-Z]+)([A-Z][a-z])/' => '\1_\2', '/([a-z\d])([A-Z])/' => '\1_\2', '/[^A-Z^a-z^0-9^\/]+/' => '-', ]; $urlized = $lowered; foreach ($replacements as $pattern => $replacement) { $replaced = preg_replace($pattern, $replacement, $urlized); if ($replaced === null) { throw new RuntimeException(sprintf( 'preg_replace returned null for value "%s"', $urlized )); } $urlized = $replaced; } return trim($urlized, '-'); } /** * Returns a word in singular form. * * @param string $word The word in plural form. * * @return string The word in singular form. */ public function singularize(string $word): string { return $this->singularizer->inflect($word); } /** * Returns a word in plural form. * * @param string $word The word in singular form. * * @return string The word in plural form. */ public function pluralize(string $word): string { return $this->pluralizer->inflect($word); } } inflector/lib/Doctrine/Inflector/WordInflector.php 0000644 00000000217 15021222234 0016256 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; interface WordInflector { public function inflect(string $word): string; } inflector/lib/Doctrine/Inflector/InflectorFactory.php 0000644 00000002616 15021222234 0016757 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; use Doctrine\Inflector\Rules\English; use Doctrine\Inflector\Rules\French; use Doctrine\Inflector\Rules\NorwegianBokmal; use Doctrine\Inflector\Rules\Portuguese; use Doctrine\Inflector\Rules\Spanish; use Doctrine\Inflector\Rules\Turkish; use InvalidArgumentException; use function sprintf; final class InflectorFactory { public static function create(): LanguageInflectorFactory { return self::createForLanguage(Language::ENGLISH); } public static function createForLanguage(string $language): LanguageInflectorFactory { switch ($language) { case Language::ENGLISH: return new English\InflectorFactory(); case Language::FRENCH: return new French\InflectorFactory(); case Language::NORWEGIAN_BOKMAL: return new NorwegianBokmal\InflectorFactory(); case Language::PORTUGUESE: return new Portuguese\InflectorFactory(); case Language::SPANISH: return new Spanish\InflectorFactory(); case Language::TURKISH: return new Turkish\InflectorFactory(); default: throw new InvalidArgumentException(sprintf( 'Language "%s" is not supported.', $language )); } } } inflector/lib/Doctrine/Inflector/LanguageInflectorFactory.php 0000644 00000001445 15021222234 0020422 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; use Doctrine\Inflector\Rules\Ruleset; interface LanguageInflectorFactory { /** * Applies custom rules for singularisation * * @param bool $reset If true, will unset default inflections for all new rules * * @return $this */ public function withSingularRules(?Ruleset $singularRules, bool $reset = false): self; /** * Applies custom rules for pluralisation * * @param bool $reset If true, will unset default inflections for all new rules * * @return $this */ public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): self; /** * Builds the inflector instance with all applicable rules */ public function build(): Inflector; } inflector/lib/Doctrine/Inflector/Language.php 0000644 00000000656 15021222234 0015227 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; final class Language { public const ENGLISH = 'english'; public const FRENCH = 'french'; public const NORWEGIAN_BOKMAL = 'norwegian-bokmal'; public const PORTUGUESE = 'portuguese'; public const SPANISH = 'spanish'; public const TURKISH = 'turkish'; private function __construct() { } } inflector/lib/Doctrine/Inflector/Rules/Patterns.php 0000644 00000001270 15021222234 0016367 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; use function array_map; use function implode; use function preg_match; class Patterns { /** @var Pattern[] */ private $patterns; /** @var string */ private $regex; public function __construct(Pattern ...$patterns) { $this->patterns = $patterns; $patterns = array_map(static function (Pattern $pattern): string { return $pattern->getPattern(); }, $this->patterns); $this->regex = '/^(?:' . implode('|', $patterns) . ')$/i'; } public function matches(string $word): bool { return preg_match($this->regex, $word, $regs) === 1; } } inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php 0000644 00000001144 15021222234 0022106 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\NorwegianBokmal; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern('barn'); yield new Pattern('fjell'); yield new Pattern('hus'); } } inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/InflectorFactory.php 0000644 00000000725 15021222234 0023127 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\NorwegianBokmal; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Inflectible.php 0000644 00000001657 15021222234 0022077 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\NorwegianBokmal; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('/re$/i'), 'r'); yield new Transformation(new Pattern('/er$/i'), ''); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('/e$/i'), 'er'); yield new Transformation(new Pattern('/r$/i'), 're'); yield new Transformation(new Pattern('/$/'), 'er'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('konto'), new Word('konti')); } } inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Rules.php 0000644 00000001562 15021222234 0020744 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\NorwegianBokmal; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php 0000644 00000001147 15021222234 0020437 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Spanish; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern('lunes'); yield new Pattern('rompecabezas'); yield new Pattern('crisis'); } } inflector/lib/Doctrine/Inflector/Rules/Spanish/InflectorFactory.php 0000644 00000000715 15021222234 0021454 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Spanish; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php 0000644 00000003471 15021222234 0020421 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Spanish; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('/ereses$/'), 'erés'); yield new Transformation(new Pattern('/iones$/'), 'ión'); yield new Transformation(new Pattern('/ces$/'), 'z'); yield new Transformation(new Pattern('/es$/'), ''); yield new Transformation(new Pattern('/s$/'), ''); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('/ú([sn])$/i'), 'u\1es'); yield new Transformation(new Pattern('/ó([sn])$/i'), 'o\1es'); yield new Transformation(new Pattern('/í([sn])$/i'), 'i\1es'); yield new Transformation(new Pattern('/é([sn])$/i'), 'e\1es'); yield new Transformation(new Pattern('/á([sn])$/i'), 'a\1es'); yield new Transformation(new Pattern('/z$/i'), 'ces'); yield new Transformation(new Pattern('/([aeiou]s)$/i'), '\1'); yield new Transformation(new Pattern('/([^aeéiou])$/i'), '\1es'); yield new Transformation(new Pattern('/$/'), 's'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('el'), new Word('los')); yield new Substitution(new Word('papá'), new Word('papás')); yield new Substitution(new Word('mamá'), new Word('mamás')); yield new Substitution(new Word('sofá'), new Word('sofás')); yield new Substitution(new Word('mes'), new Word('meses')); } } inflector/lib/Doctrine/Inflector/Rules/Spanish/Rules.php 0000644 00000001552 15021222234 0017271 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Spanish; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/Transformations.php 0000644 00000001210 15021222234 0017752 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; use Doctrine\Inflector\WordInflector; class Transformations implements WordInflector { /** @var Transformation[] */ private $transformations; public function __construct(Transformation ...$transformations) { $this->transformations = $transformations; } public function inflect(string $word): string { foreach ($this->transformations as $transformation) { if ($transformation->getPattern()->matches($word)) { return $transformation->inflect($word); } } return $word; } } inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php 0000644 00000001021 15021222234 0020226 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\French; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern(''); } } inflector/lib/Doctrine/Inflector/Rules/French/InflectorFactory.php 0000644 00000000714 15021222234 0021253 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\French; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/French/Inflectible.php 0000644 00000003526 15021222234 0020222 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\French; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/'), '\1ail'); yield new Transformation(new Pattern('/ails$/'), 'ail'); yield new Transformation(new Pattern('/(journ|chev)aux$/'), '\1al'); yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/'), '\1'); yield new Transformation(new Pattern('/s$/'), ''); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('/(s|x|z)$/'), '\1'); yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)ail$/'), '\1aux'); yield new Transformation(new Pattern('/ail$/'), 'ails'); yield new Transformation(new Pattern('/(chacal|carnaval|festival|récital)$/'), '\1s'); yield new Transformation(new Pattern('/al$/'), 'aux'); yield new Transformation(new Pattern('/(bleu|émeu|landau|pneu|sarrau)$/'), '\1s'); yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|lieu|pou|au|eu|eau)$/'), '\1x'); yield new Transformation(new Pattern('/$/'), 's'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('monsieur'), new Word('messieurs')); yield new Substitution(new Word('madame'), new Word('mesdames')); yield new Substitution(new Word('mademoiselle'), new Word('mesdemoiselles')); } } inflector/lib/Doctrine/Inflector/Rules/French/Rules.php 0000644 00000001551 15021222234 0017070 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\French; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/Substitutions.php 0000644 00000002534 15021222234 0017472 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; use Doctrine\Inflector\WordInflector; use function strtolower; use function strtoupper; use function substr; class Substitutions implements WordInflector { /** @var Substitution[] */ private $substitutions; public function __construct(Substitution ...$substitutions) { foreach ($substitutions as $substitution) { $this->substitutions[$substitution->getFrom()->getWord()] = $substitution; } } public function getFlippedSubstitutions(): Substitutions { $substitutions = []; foreach ($this->substitutions as $substitution) { $substitutions[] = new Substitution( $substitution->getTo(), $substitution->getFrom() ); } return new Substitutions(...$substitutions); } public function inflect(string $word): string { $lowerWord = strtolower($word); if (isset($this->substitutions[$lowerWord])) { $firstLetterUppercase = $lowerWord[0] !== $word[0]; $toWord = $this->substitutions[$lowerWord]->getTo()->getWord(); if ($firstLetterUppercase) { return strtoupper($toWord[0]) . substr($toWord, 1); } return $toWord; } return $word; } } inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php 0000644 00000001147 15021222234 0020463 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Turkish; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern('lunes'); yield new Pattern('rompecabezas'); yield new Pattern('crisis'); } } inflector/lib/Doctrine/Inflector/Rules/Turkish/InflectorFactory.php 0000644 00000000715 15021222234 0021500 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Turkish; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php 0000644 00000001756 15021222234 0020451 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Turkish; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('/l[ae]r$/i'), ''); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('/([eöiü][^aoıueöiü]{0,6})$/u'), '\1ler'); yield new Transformation(new Pattern('/([aoıu][^aoıueöiü]{0,6})$/u'), '\1lar'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('ben'), new Word('biz')); yield new Substitution(new Word('sen'), new Word('siz')); yield new Substitution(new Word('o'), new Word('onlar')); } } inflector/lib/Doctrine/Inflector/Rules/Turkish/Rules.php 0000644 00000001552 15021222234 0017315 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Turkish; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php 0000644 00000001260 15021222234 0021170 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Portuguese; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern('tórax'); yield new Pattern('tênis'); yield new Pattern('ônibus'); yield new Pattern('lápis'); yield new Pattern('fênix'); } } inflector/lib/Doctrine/Inflector/Rules/Portuguese/InflectorFactory.php 0000644 00000000720 15021222234 0022205 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Portuguese; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php 0000644 00000013020 15021222234 0021145 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Portuguese; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('/^(g|)ases$/i'), '\1ás'); yield new Transformation(new Pattern('/(japon|escoc|ingl|dinamarqu|fregu|portugu)eses$/i'), '\1ês'); yield new Transformation(new Pattern('/(ae|ao|oe)s$/'), 'ao'); yield new Transformation(new Pattern('/(ãe|ão|õe)s$/'), 'ão'); yield new Transformation(new Pattern('/^(.*[^s]s)es$/i'), '\1'); yield new Transformation(new Pattern('/sses$/i'), 'sse'); yield new Transformation(new Pattern('/ns$/i'), 'm'); yield new Transformation(new Pattern('/(r|t|f|v)is$/i'), '\1il'); yield new Transformation(new Pattern('/uis$/i'), 'ul'); yield new Transformation(new Pattern('/ois$/i'), 'ol'); yield new Transformation(new Pattern('/eis$/i'), 'ei'); yield new Transformation(new Pattern('/éis$/i'), 'el'); yield new Transformation(new Pattern('/([^p])ais$/i'), '\1al'); yield new Transformation(new Pattern('/(r|z)es$/i'), '\1'); yield new Transformation(new Pattern('/^(á|gá)s$/i'), '\1s'); yield new Transformation(new Pattern('/([^ê])s$/i'), '\1'); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('/^(alem|c|p)ao$/i'), '\1aes'); yield new Transformation(new Pattern('/^(irm|m)ao$/i'), '\1aos'); yield new Transformation(new Pattern('/ao$/i'), 'oes'); yield new Transformation(new Pattern('/^(alem|c|p)ão$/i'), '\1ães'); yield new Transformation(new Pattern('/^(irm|m)ão$/i'), '\1ãos'); yield new Transformation(new Pattern('/ão$/i'), 'ões'); yield new Transformation(new Pattern('/^(|g)ás$/i'), '\1ases'); yield new Transformation(new Pattern('/^(japon|escoc|ingl|dinamarqu|fregu|portugu)ês$/i'), '\1eses'); yield new Transformation(new Pattern('/m$/i'), 'ns'); yield new Transformation(new Pattern('/([^aeou])il$/i'), '\1is'); yield new Transformation(new Pattern('/ul$/i'), 'uis'); yield new Transformation(new Pattern('/ol$/i'), 'ois'); yield new Transformation(new Pattern('/el$/i'), 'eis'); yield new Transformation(new Pattern('/al$/i'), 'ais'); yield new Transformation(new Pattern('/(z|r)$/i'), '\1es'); yield new Transformation(new Pattern('/(s)$/i'), '\1'); yield new Transformation(new Pattern('/$/'), 's'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('abdomen'), new Word('abdomens')); yield new Substitution(new Word('alemão'), new Word('alemães')); yield new Substitution(new Word('artesã'), new Word('artesãos')); yield new Substitution(new Word('álcool'), new Word('álcoois')); yield new Substitution(new Word('árvore'), new Word('árvores')); yield new Substitution(new Word('bencão'), new Word('bencãos')); yield new Substitution(new Word('cão'), new Word('cães')); yield new Substitution(new Word('campus'), new Word('campi')); yield new Substitution(new Word('cadáver'), new Word('cadáveres')); yield new Substitution(new Word('capelão'), new Word('capelães')); yield new Substitution(new Word('capitão'), new Word('capitães')); yield new Substitution(new Word('chão'), new Word('chãos')); yield new Substitution(new Word('charlatão'), new Word('charlatães')); yield new Substitution(new Word('cidadão'), new Word('cidadãos')); yield new Substitution(new Word('consul'), new Word('consules')); yield new Substitution(new Word('cristão'), new Word('cristãos')); yield new Substitution(new Word('difícil'), new Word('difíceis')); yield new Substitution(new Word('email'), new Word('emails')); yield new Substitution(new Word('escrivão'), new Word('escrivães')); yield new Substitution(new Word('fóssil'), new Word('fósseis')); yield new Substitution(new Word('gás'), new Word('gases')); yield new Substitution(new Word('germens'), new Word('germen')); yield new Substitution(new Word('grão'), new Word('grãos')); yield new Substitution(new Word('hífen'), new Word('hífens')); yield new Substitution(new Word('irmão'), new Word('irmãos')); yield new Substitution(new Word('liquens'), new Word('liquen')); yield new Substitution(new Word('mal'), new Word('males')); yield new Substitution(new Word('mão'), new Word('mãos')); yield new Substitution(new Word('orfão'), new Word('orfãos')); yield new Substitution(new Word('país'), new Word('países')); yield new Substitution(new Word('pai'), new Word('pais')); yield new Substitution(new Word('pão'), new Word('pães')); yield new Substitution(new Word('projétil'), new Word('projéteis')); yield new Substitution(new Word('réptil'), new Word('répteis')); yield new Substitution(new Word('sacristão'), new Word('sacristães')); yield new Substitution(new Word('sotão'), new Word('sotãos')); yield new Substitution(new Word('tabelião'), new Word('tabeliães')); } } inflector/lib/Doctrine/Inflector/Rules/Portuguese/Rules.php 0000644 00000001555 15021222234 0020031 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Portuguese; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/Word.php 0000644 00000000446 15021222234 0015506 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; class Word { /** @var string */ private $word; public function __construct(string $word) { $this->word = $word; } public function getWord(): string { return $this->word; } } inflector/lib/Doctrine/Inflector/Rules/Ruleset.php 0000644 00000001411 15021222234 0016207 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; class Ruleset { /** @var Transformations */ private $regular; /** @var Patterns */ private $uninflected; /** @var Substitutions */ private $irregular; public function __construct(Transformations $regular, Patterns $uninflected, Substitutions $irregular) { $this->regular = $regular; $this->uninflected = $uninflected; $this->irregular = $irregular; } public function getRegular(): Transformations { return $this->regular; } public function getUninflected(): Patterns { return $this->uninflected; } public function getIrregular(): Substitutions { return $this->irregular; } } inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php 0000644 00000014617 15021222234 0020431 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\English; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); yield new Pattern('.*ss'); yield new Pattern('clothes'); yield new Pattern('data'); yield new Pattern('fascia'); yield new Pattern('fuchsia'); yield new Pattern('galleria'); yield new Pattern('mafia'); yield new Pattern('militia'); yield new Pattern('pants'); yield new Pattern('petunia'); yield new Pattern('sepia'); yield new Pattern('trivia'); yield new Pattern('utopia'); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); yield new Pattern('people'); yield new Pattern('trivia'); yield new Pattern('\w+ware$'); yield new Pattern('media'); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern('\w+media'); yield new Pattern('advice'); yield new Pattern('aircraft'); yield new Pattern('amoyese'); yield new Pattern('art'); yield new Pattern('audio'); yield new Pattern('baggage'); yield new Pattern('bison'); yield new Pattern('borghese'); yield new Pattern('bream'); yield new Pattern('breeches'); yield new Pattern('britches'); yield new Pattern('buffalo'); yield new Pattern('butter'); yield new Pattern('cantus'); yield new Pattern('carp'); yield new Pattern('cattle'); yield new Pattern('chassis'); yield new Pattern('clippers'); yield new Pattern('clothing'); yield new Pattern('coal'); yield new Pattern('cod'); yield new Pattern('coitus'); yield new Pattern('compensation'); yield new Pattern('congoese'); yield new Pattern('contretemps'); yield new Pattern('coreopsis'); yield new Pattern('corps'); yield new Pattern('cotton'); yield new Pattern('data'); yield new Pattern('debris'); yield new Pattern('deer'); yield new Pattern('diabetes'); yield new Pattern('djinn'); yield new Pattern('education'); yield new Pattern('eland'); yield new Pattern('elk'); yield new Pattern('emoji'); yield new Pattern('equipment'); yield new Pattern('evidence'); yield new Pattern('faroese'); yield new Pattern('feedback'); yield new Pattern('fish'); yield new Pattern('flounder'); yield new Pattern('flour'); yield new Pattern('foochowese'); yield new Pattern('food'); yield new Pattern('furniture'); yield new Pattern('gallows'); yield new Pattern('genevese'); yield new Pattern('genoese'); yield new Pattern('gilbertese'); yield new Pattern('gold'); yield new Pattern('headquarters'); yield new Pattern('herpes'); yield new Pattern('hijinks'); yield new Pattern('homework'); yield new Pattern('hottentotese'); yield new Pattern('impatience'); yield new Pattern('information'); yield new Pattern('innings'); yield new Pattern('jackanapes'); yield new Pattern('jeans'); yield new Pattern('jedi'); yield new Pattern('kin'); yield new Pattern('kiplingese'); yield new Pattern('knowledge'); yield new Pattern('kongoese'); yield new Pattern('leather'); yield new Pattern('love'); yield new Pattern('lucchese'); yield new Pattern('luggage'); yield new Pattern('mackerel'); yield new Pattern('Maltese'); yield new Pattern('management'); yield new Pattern('metadata'); yield new Pattern('mews'); yield new Pattern('money'); yield new Pattern('moose'); yield new Pattern('mumps'); yield new Pattern('music'); yield new Pattern('nankingese'); yield new Pattern('news'); yield new Pattern('nexus'); yield new Pattern('niasese'); yield new Pattern('nutrition'); yield new Pattern('offspring'); yield new Pattern('oil'); yield new Pattern('patience'); yield new Pattern('pekingese'); yield new Pattern('piedmontese'); yield new Pattern('pincers'); yield new Pattern('pistoiese'); yield new Pattern('plankton'); yield new Pattern('pliers'); yield new Pattern('pokemon'); yield new Pattern('police'); yield new Pattern('polish'); yield new Pattern('portuguese'); yield new Pattern('proceedings'); yield new Pattern('progress'); yield new Pattern('rabies'); yield new Pattern('rain'); yield new Pattern('research'); yield new Pattern('rhinoceros'); yield new Pattern('rice'); yield new Pattern('salmon'); yield new Pattern('sand'); yield new Pattern('sarawakese'); yield new Pattern('scissors'); yield new Pattern('sea[- ]bass'); yield new Pattern('series'); yield new Pattern('shavese'); yield new Pattern('shears'); yield new Pattern('sheep'); yield new Pattern('siemens'); yield new Pattern('silk'); yield new Pattern('sms'); yield new Pattern('soap'); yield new Pattern('social media'); yield new Pattern('spam'); yield new Pattern('species'); yield new Pattern('staff'); yield new Pattern('sugar'); yield new Pattern('swine'); yield new Pattern('talent'); yield new Pattern('toothpaste'); yield new Pattern('traffic'); yield new Pattern('travel'); yield new Pattern('trousers'); yield new Pattern('trout'); yield new Pattern('tuna'); yield new Pattern('us'); yield new Pattern('vermontese'); yield new Pattern('vinegar'); yield new Pattern('weather'); yield new Pattern('wenchowese'); yield new Pattern('wheat'); yield new Pattern('whiting'); yield new Pattern('wildebeest'); yield new Pattern('wood'); yield new Pattern('wool'); yield new Pattern('yengeese'); } } inflector/lib/Doctrine/Inflector/Rules/English/InflectorFactory.php 0000644 00000000715 15021222234 0021440 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\English; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/English/Inflectible.php 0000644 00000027137 15021222234 0020412 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\English; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('(s)tatuses$'), '\1\2tatus'); yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatus'); yield new Transformation(new Pattern('(c)ampus$'), '\1\2ampus'); yield new Transformation(new Pattern('^(.*)(menu)s$'), '\1\2'); yield new Transformation(new Pattern('(quiz)zes$'), '\\1'); yield new Transformation(new Pattern('(matr)ices$'), '\1ix'); yield new Transformation(new Pattern('(vert|ind)ices$'), '\1ex'); yield new Transformation(new Pattern('^(ox)en'), '\1'); yield new Transformation(new Pattern('(alias)(es)*$'), '\1'); yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)oes$'), '\1o'); yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$'), '\1us'); yield new Transformation(new Pattern('([ftw]ax)es'), '\1'); yield new Transformation(new Pattern('(analys|ax|cris|test|thes)es$'), '\1is'); yield new Transformation(new Pattern('(shoe|slave)s$'), '\1'); yield new Transformation(new Pattern('(o)es$'), '\1'); yield new Transformation(new Pattern('ouses$'), 'ouse'); yield new Transformation(new Pattern('([^a])uses$'), '\1us'); yield new Transformation(new Pattern('([m|l])ice$'), '\1ouse'); yield new Transformation(new Pattern('(x|ch|ss|sh)es$'), '\1'); yield new Transformation(new Pattern('(m)ovies$'), '\1\2ovie'); yield new Transformation(new Pattern('(s)eries$'), '\1\2eries'); yield new Transformation(new Pattern('([^aeiouy]|qu)ies$'), '\1y'); yield new Transformation(new Pattern('([lr])ves$'), '\1f'); yield new Transformation(new Pattern('(tive)s$'), '\1'); yield new Transformation(new Pattern('(hive)s$'), '\1'); yield new Transformation(new Pattern('(drive)s$'), '\1'); yield new Transformation(new Pattern('(dive)s$'), '\1'); yield new Transformation(new Pattern('(olive)s$'), '\1'); yield new Transformation(new Pattern('([^fo])ves$'), '\1fe'); yield new Transformation(new Pattern('(^analy)ses$'), '\1sis'); yield new Transformation(new Pattern('(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$'), '\1\2sis'); yield new Transformation(new Pattern('(tax)a$'), '\1on'); yield new Transformation(new Pattern('(c)riteria$'), '\1riterion'); yield new Transformation(new Pattern('([ti])a(?<!regatta)$'), '\1um'); yield new Transformation(new Pattern('(p)eople$'), '\1\2erson'); yield new Transformation(new Pattern('(m)en$'), '\1an'); yield new Transformation(new Pattern('(c)hildren$'), '\1\2hild'); yield new Transformation(new Pattern('(f)eet$'), '\1oot'); yield new Transformation(new Pattern('(n)ews$'), '\1\2ews'); yield new Transformation(new Pattern('eaus$'), 'eau'); yield new Transformation(new Pattern('^tights$'), 'tights'); yield new Transformation(new Pattern('^shorts$'), 'shorts'); yield new Transformation(new Pattern('s$'), ''); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatuses'); yield new Transformation(new Pattern('(quiz)$'), '\1zes'); yield new Transformation(new Pattern('^(ox)$'), '\1\2en'); yield new Transformation(new Pattern('([m|l])ouse$'), '\1ice'); yield new Transformation(new Pattern('(matr|vert|ind)(ix|ex)$'), '\1ices'); yield new Transformation(new Pattern('(x|ch|ss|sh)$'), '\1es'); yield new Transformation(new Pattern('([^aeiouy]|qu)y$'), '\1ies'); yield new Transformation(new Pattern('(hive|gulf)$'), '\1s'); yield new Transformation(new Pattern('(?:([^f])fe|([lr])f)$'), '\1\2ves'); yield new Transformation(new Pattern('sis$'), 'ses'); yield new Transformation(new Pattern('([ti])um$'), '\1a'); yield new Transformation(new Pattern('(tax)on$'), '\1a'); yield new Transformation(new Pattern('(c)riterion$'), '\1riteria'); yield new Transformation(new Pattern('(p)erson$'), '\1eople'); yield new Transformation(new Pattern('(m)an$'), '\1en'); yield new Transformation(new Pattern('(c)hild$'), '\1hildren'); yield new Transformation(new Pattern('(f)oot$'), '\1eet'); yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)o$'), '\1\2oes'); yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$'), '\1i'); yield new Transformation(new Pattern('us$'), 'uses'); yield new Transformation(new Pattern('(alias)$'), '\1es'); yield new Transformation(new Pattern('(analys|ax|cris|test|thes)is$'), '\1es'); yield new Transformation(new Pattern('s$'), 's'); yield new Transformation(new Pattern('^$'), ''); yield new Transformation(new Pattern('$'), 's'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('atlas'), new Word('atlases')); yield new Substitution(new Word('axis'), new Word('axes')); yield new Substitution(new Word('axe'), new Word('axes')); yield new Substitution(new Word('beef'), new Word('beefs')); yield new Substitution(new Word('blouse'), new Word('blouses')); yield new Substitution(new Word('brother'), new Word('brothers')); yield new Substitution(new Word('cafe'), new Word('cafes')); yield new Substitution(new Word('cave'), new Word('caves')); yield new Substitution(new Word('chateau'), new Word('chateaux')); yield new Substitution(new Word('niveau'), new Word('niveaux')); yield new Substitution(new Word('child'), new Word('children')); yield new Substitution(new Word('canvas'), new Word('canvases')); yield new Substitution(new Word('cookie'), new Word('cookies')); yield new Substitution(new Word('brownie'), new Word('brownies')); yield new Substitution(new Word('corpus'), new Word('corpuses')); yield new Substitution(new Word('cow'), new Word('cows')); yield new Substitution(new Word('criterion'), new Word('criteria')); yield new Substitution(new Word('curriculum'), new Word('curricula')); yield new Substitution(new Word('demo'), new Word('demos')); yield new Substitution(new Word('domino'), new Word('dominoes')); yield new Substitution(new Word('echo'), new Word('echoes')); yield new Substitution(new Word('epoch'), new Word('epochs')); yield new Substitution(new Word('foot'), new Word('feet')); yield new Substitution(new Word('fungus'), new Word('fungi')); yield new Substitution(new Word('ganglion'), new Word('ganglions')); yield new Substitution(new Word('gas'), new Word('gases')); yield new Substitution(new Word('genie'), new Word('genies')); yield new Substitution(new Word('genus'), new Word('genera')); yield new Substitution(new Word('goose'), new Word('geese')); yield new Substitution(new Word('graffito'), new Word('graffiti')); yield new Substitution(new Word('hippopotamus'), new Word('hippopotami')); yield new Substitution(new Word('hoof'), new Word('hoofs')); yield new Substitution(new Word('human'), new Word('humans')); yield new Substitution(new Word('iris'), new Word('irises')); yield new Substitution(new Word('larva'), new Word('larvae')); yield new Substitution(new Word('leaf'), new Word('leaves')); yield new Substitution(new Word('lens'), new Word('lenses')); yield new Substitution(new Word('loaf'), new Word('loaves')); yield new Substitution(new Word('man'), new Word('men')); yield new Substitution(new Word('medium'), new Word('media')); yield new Substitution(new Word('memorandum'), new Word('memoranda')); yield new Substitution(new Word('money'), new Word('monies')); yield new Substitution(new Word('mongoose'), new Word('mongooses')); yield new Substitution(new Word('motto'), new Word('mottoes')); yield new Substitution(new Word('move'), new Word('moves')); yield new Substitution(new Word('mythos'), new Word('mythoi')); yield new Substitution(new Word('niche'), new Word('niches')); yield new Substitution(new Word('nucleus'), new Word('nuclei')); yield new Substitution(new Word('numen'), new Word('numina')); yield new Substitution(new Word('occiput'), new Word('occiputs')); yield new Substitution(new Word('octopus'), new Word('octopuses')); yield new Substitution(new Word('opus'), new Word('opuses')); yield new Substitution(new Word('ox'), new Word('oxen')); yield new Substitution(new Word('passerby'), new Word('passersby')); yield new Substitution(new Word('penis'), new Word('penises')); yield new Substitution(new Word('person'), new Word('people')); yield new Substitution(new Word('plateau'), new Word('plateaux')); yield new Substitution(new Word('runner-up'), new Word('runners-up')); yield new Substitution(new Word('safe'), new Word('safes')); yield new Substitution(new Word('sex'), new Word('sexes')); yield new Substitution(new Word('sieve'), new Word('sieves')); yield new Substitution(new Word('soliloquy'), new Word('soliloquies')); yield new Substitution(new Word('son-in-law'), new Word('sons-in-law')); yield new Substitution(new Word('syllabus'), new Word('syllabi')); yield new Substitution(new Word('testis'), new Word('testes')); yield new Substitution(new Word('thief'), new Word('thieves')); yield new Substitution(new Word('tooth'), new Word('teeth')); yield new Substitution(new Word('tornado'), new Word('tornadoes')); yield new Substitution(new Word('trilby'), new Word('trilbys')); yield new Substitution(new Word('turf'), new Word('turfs')); yield new Substitution(new Word('valve'), new Word('valves')); yield new Substitution(new Word('volcano'), new Word('volcanoes')); yield new Substitution(new Word('abuse'), new Word('abuses')); yield new Substitution(new Word('avalanche'), new Word('avalanches')); yield new Substitution(new Word('cache'), new Word('caches')); yield new Substitution(new Word('criterion'), new Word('criteria')); yield new Substitution(new Word('curve'), new Word('curves')); yield new Substitution(new Word('emphasis'), new Word('emphases')); yield new Substitution(new Word('foe'), new Word('foes')); yield new Substitution(new Word('grave'), new Word('graves')); yield new Substitution(new Word('hoax'), new Word('hoaxes')); yield new Substitution(new Word('medium'), new Word('media')); yield new Substitution(new Word('neurosis'), new Word('neuroses')); yield new Substitution(new Word('save'), new Word('saves')); yield new Substitution(new Word('wave'), new Word('waves')); yield new Substitution(new Word('oasis'), new Word('oases')); yield new Substitution(new Word('valve'), new Word('valves')); yield new Substitution(new Word('zombie'), new Word('zombies')); } } inflector/lib/Doctrine/Inflector/Rules/English/Rules.php 0000644 00000001552 15021222234 0017255 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\English; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/Pattern.php 0000644 00000001424 15021222234 0016205 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; use function preg_match; final class Pattern { /** @var string */ private $pattern; /** @var string */ private $regex; public function __construct(string $pattern) { $this->pattern = $pattern; if (isset($this->pattern[0]) && $this->pattern[0] === '/') { $this->regex = $this->pattern; } else { $this->regex = '/' . $this->pattern . '/i'; } } public function getPattern(): string { return $this->pattern; } public function getRegex(): string { return $this->regex; } public function matches(string $word): bool { return preg_match($this->getRegex(), $word) === 1; } } inflector/lib/Doctrine/Inflector/Rules/Substitution.php 0000644 00000000703 15021222234 0017303 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; final class Substitution { /** @var Word */ private $from; /** @var Word */ private $to; public function __construct(Word $from, Word $to) { $this->from = $from; $this->to = $to; } public function getFrom(): Word { return $this->from; } public function getTo(): Word { return $this->to; } } inflector/lib/Doctrine/Inflector/Rules/Transformation.php 0000644 00000001426 15021222234 0017600 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; use Doctrine\Inflector\WordInflector; use function preg_replace; final class Transformation implements WordInflector { /** @var Pattern */ private $pattern; /** @var string */ private $replacement; public function __construct(Pattern $pattern, string $replacement) { $this->pattern = $pattern; $this->replacement = $replacement; } public function getPattern(): Pattern { return $this->pattern; } public function getReplacement(): string { return $this->replacement; } public function inflect(string $word): string { return (string) preg_replace($this->pattern->getRegex(), $this->replacement, $word); } } inflector/lib/Doctrine/Inflector/CachedWordInflector.php 0000644 00000000777 15021222234 0017361 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; class CachedWordInflector implements WordInflector { /** @var WordInflector */ private $wordInflector; /** @var string[] */ private $cache = []; public function __construct(WordInflector $wordInflector) { $this->wordInflector = $wordInflector; } public function inflect(string $word): string { return $this->cache[$word] ?? $this->cache[$word] = $this->wordInflector->inflect($word); } } inflector/README.md 0000644 00000001015 15021222234 0007776 0 ustar 00 # Doctrine Inflector Doctrine Inflector is a small library that can perform string manipulations with regard to uppercase/lowercase and singular/plural forms of words. [](https://github.com/doctrine/inflector/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.0.x) [](https://codecov.io/gh/doctrine/inflector/branch/2.0.x) inflector/LICENSE 0000644 00000002051 15021222234 0007525 0 ustar 00 Copyright (c) 2006-2015 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. instantiator/composer.json 0000644 00000002755 15021222234 0012007 0 ustar 00 { "name": "doctrine/instantiator", "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", "type": "library", "license": "MIT", "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "instantiate", "constructor" ], "authors": [ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", "homepage": "https://ocramius.github.io/" } ], "require": { "php": "^8.1" }, "require-dev": { "ext-phar": "*", "ext-pdo": "*", "doctrine/coding-standard": "^11", "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^1.9.4", "phpstan/phpstan-phpunit": "^1.3", "phpunit/phpunit": "^9.5.27", "vimeo/psalm": "^5.4" }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "autoload-dev": { "psr-0": { "DoctrineTest\\InstantiatorPerformance\\": "tests", "DoctrineTest\\InstantiatorTest\\": "tests", "DoctrineTest\\InstantiatorTestAsset\\": "tests" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } instantiator/docs/en/index.rst 0000644 00000003337 15021222234 0012455 0 ustar 00 Introduction ============ This library provides a way of avoiding usage of constructors when instantiating PHP classes. Installation ============ The suggested installation method is via `composer`_: .. code-block:: console $ composer require doctrine/instantiator Usage ===== The instantiator is able to create new instances of any class without using the constructor or any API of the class itself: .. code-block:: php <?php use Doctrine\Instantiator\Instantiator; use App\Entities\User; $instantiator = new Instantiator(); $user = $instantiator->instantiate(User::class); Contributing ============ - Follow the `Doctrine Coding Standard`_ - The project will follow strict `object calisthenics`_ - Any contribution must provide tests for additional introduced conditions - Any un-confirmed issue needs a failing test case before being accepted - Pull requests must be sent from a new hotfix/feature branch, not from ``master``. Testing ======= The PHPUnit version to be used is the one installed as a dev- dependency via composer: .. code-block:: console $ ./vendor/bin/phpunit Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement won’t be merged. Credits ======= This library was migrated from `ocramius/instantiator`_, which has been donated to the doctrine organization, and which is now deprecated in favour of this package. .. _composer: https://getcomposer.org/ .. _CONTRIBUTING.md: CONTRIBUTING.md .. _ocramius/instantiator: https://github.com/Ocramius/Instantiator .. _Doctrine Coding Standard: https://github.com/doctrine/coding-standard .. _object calisthenics: http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php instantiator/docs/en/sidebar.rst 0000644 00000000046 15021222234 0012751 0 ustar 00 .. toctree:: :depth: 3 index instantiator/CONTRIBUTING.md 0000644 00000002010 15021222234 0011476 0 ustar 00 # Contributing * Follow the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard) * The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php) * Any contribution must provide tests for additional introduced conditions * Any un-confirmed issue needs a failing test case before being accepted * Pull requests must be sent from a new hotfix/feature branch, not from `master`. ## Installation To install the project and run the tests, you need to clone it first: ```sh $ git clone git://github.com/doctrine/instantiator.git ``` You will then need to run a composer installation: ```sh $ cd Instantiator $ curl -s https://getcomposer.org/installer | php $ php composer.phar update ``` ## Testing The PHPUnit version to be used is the one installed as a dev- dependency via composer: ```sh $ ./vendor/bin/phpunit ``` Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement won't be merged. instantiator/psalm.xml 0000644 00000000721 15021222234 0011112 0 ustar 00 <?xml version="1.0"?> <psalm errorLevel="7" phpVersion="8.2" resolveFromConfigFile="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" > <projectFiles> <directory name="src" /> <ignoreFiles> <directory name="vendor" /> </ignoreFiles> </projectFiles> </psalm> instantiator/.doctrine-project.json 0000644 00000002021 15021222234 0013473 0 ustar 00 { "active": true, "name": "Instantiator", "slug": "instantiator", "docsSlug": "doctrine-instantiator", "codePath": "/src", "versions": [ { "name": "1.5", "branchName": "1.5.x", "slug": "latest", "upcoming": true }, { "name": "1.4", "branchName": "1.4.x", "slug": "1.4", "aliases": [ "current", "stable" ], "maintained": true, "current": true }, { "name": "1.3", "branchName": "1.3.x", "slug": "1.3", "maintained": false }, { "name": "1.2", "branchName": "1.2.x", "slug": "1.2" }, { "name": "1.1", "branchName": "1.1.x", "slug": "1.1" }, { "name": "1.0", "branchName": "1.0.x", "slug": "1.0" } ] } instantiator/src/Doctrine/Instantiator/Instantiator.php 0000644 00000016333 15021222234 0017467 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Instantiator; use ArrayIterator; use Doctrine\Instantiator\Exception\ExceptionInterface; use Doctrine\Instantiator\Exception\InvalidArgumentException; use Doctrine\Instantiator\Exception\UnexpectedValueException; use Exception; use ReflectionClass; use ReflectionException; use Serializable; use function class_exists; use function enum_exists; use function is_subclass_of; use function restore_error_handler; use function set_error_handler; use function sprintf; use function strlen; use function unserialize; final class Instantiator implements InstantiatorInterface { /** * Markers used internally by PHP to define whether {@see \unserialize} should invoke * the method {@see \Serializable::unserialize()} when dealing with classes implementing * the {@see \Serializable} interface. * * @deprecated This constant will be private in 2.0 */ private const SERIALIZATION_FORMAT_USE_UNSERIALIZER = 'C'; private const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O'; /** * Used to instantiate specific classes, indexed by class name. * * @var callable[] */ private static array $cachedInstantiators = []; /** * Array of objects that can directly be cloned, indexed by class name. * * @var object[] */ private static array $cachedCloneables = []; /** * @phpstan-param class-string<T> $className * * @phpstan-return T * * @throws ExceptionInterface * * @template T of object */ public function instantiate(string $className): object { if (isset(self::$cachedCloneables[$className])) { /** @phpstan-var T */ $cachedCloneable = self::$cachedCloneables[$className]; return clone $cachedCloneable; } if (isset(self::$cachedInstantiators[$className])) { $factory = self::$cachedInstantiators[$className]; return $factory(); } return $this->buildAndCacheFromFactory($className); } /** * Builds the requested object and caches it in static properties for performance * * @phpstan-param class-string<T> $className * * @phpstan-return T * * @template T of object */ private function buildAndCacheFromFactory(string $className): object { $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className); $instance = $factory(); if ($this->isSafeToClone(new ReflectionClass($instance))) { self::$cachedCloneables[$className] = clone $instance; } return $instance; } /** * Builds a callable capable of instantiating the given $className without * invoking its constructor. * * @phpstan-param class-string<T> $className * * @phpstan-return callable(): T * * @throws InvalidArgumentException * @throws UnexpectedValueException * @throws ReflectionException * * @template T of object */ private function buildFactory(string $className): callable { $reflectionClass = $this->getReflectionClass($className); if ($this->isInstantiableViaReflection($reflectionClass)) { return [$reflectionClass, 'newInstanceWithoutConstructor']; } $serializedString = sprintf( '%s:%d:"%s":0:{}', is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, strlen($className), $className, ); $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); return static fn () => unserialize($serializedString); } /** * @phpstan-param class-string<T> $className * * @phpstan-return ReflectionClass<T> * * @throws InvalidArgumentException * @throws ReflectionException * * @template T of object */ private function getReflectionClass(string $className): ReflectionClass { if (! class_exists($className)) { throw InvalidArgumentException::fromNonExistingClass($className); } if (enum_exists($className, false)) { throw InvalidArgumentException::fromEnum($className); } $reflection = new ReflectionClass($className); if ($reflection->isAbstract()) { throw InvalidArgumentException::fromAbstractClass($reflection); } return $reflection; } /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @throws UnexpectedValueException * * @template T of object */ private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString): void { set_error_handler(static function (int $code, string $message, string $file, int $line) use ($reflectionClass, &$error): bool { $error = UnexpectedValueException::fromUncleanUnSerialization( $reflectionClass, $message, $code, $file, $line, ); return true; }); try { $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); } finally { restore_error_handler(); } if ($error) { throw $error; } } /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @throws UnexpectedValueException * * @template T of object */ private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString): void { try { unserialize($serializedString); } catch (Exception $exception) { throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); } } /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ private function isInstantiableViaReflection(ReflectionClass $reflectionClass): bool { return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); } /** * Verifies whether the given class is to be considered internal * * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ private function hasInternalAncestors(ReflectionClass $reflectionClass): bool { do { if ($reflectionClass->isInternal()) { return true; } $reflectionClass = $reflectionClass->getParentClass(); } while ($reflectionClass); return false; } /** * Checks if a class is cloneable * * Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects. * * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ private function isSafeToClone(ReflectionClass $reflectionClass): bool { return $reflectionClass->isCloneable() && ! $reflectionClass->hasMethod('__clone') && ! $reflectionClass->isSubclassOf(ArrayIterator::class); } } instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php 0000644 00000003167 15021222234 0023727 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Instantiator\Exception; use Exception; use ReflectionClass; use UnexpectedValueException as BaseUnexpectedValueException; use function sprintf; /** * Exception for given parameters causing invalid/unexpected state on instantiation */ class UnexpectedValueException extends BaseUnexpectedValueException implements ExceptionInterface { /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ public static function fromSerializationTriggeredException( ReflectionClass $reflectionClass, Exception $exception, ): self { return new self( sprintf( 'An exception was raised while trying to instantiate an instance of "%s" via un-serialization', $reflectionClass->getName(), ), 0, $exception, ); } /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ public static function fromUncleanUnSerialization( ReflectionClass $reflectionClass, string $errorString, int $errorCode, string $errorFile, int $errorLine, ): self { return new self( sprintf( 'Could not produce an instance of "%s" via un-serialization, since an error was triggered ' . 'in file "%s" at line "%d"', $reflectionClass->getName(), $errorFile, $errorLine, ), 0, new Exception($errorString, $errorCode), ); } } instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.php 0000644 00000002764 15021222234 0023721 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Instantiator\Exception; use InvalidArgumentException as BaseInvalidArgumentException; use ReflectionClass; use function interface_exists; use function sprintf; use function trait_exists; /** * Exception for invalid arguments provided to the instantiator */ class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface { public static function fromNonExistingClass(string $className): self { if (interface_exists($className)) { return new self(sprintf('The provided type "%s" is an interface, and cannot be instantiated', $className)); } if (trait_exists($className)) { return new self(sprintf('The provided type "%s" is a trait, and cannot be instantiated', $className)); } return new self(sprintf('The provided class "%s" does not exist', $className)); } /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ public static function fromAbstractClass(ReflectionClass $reflectionClass): self { return new self(sprintf( 'The provided class "%s" is abstract, and cannot be instantiated', $reflectionClass->getName(), )); } public static function fromEnum(string $className): self { return new self(sprintf( 'The provided class "%s" is an enum, and cannot be instantiated', $className, )); } } instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php 0000644 00000000332 15021222234 0022515 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Instantiator\Exception; use Throwable; /** * Base exception marker interface for the instantiator component */ interface ExceptionInterface extends Throwable { } instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php 0000644 00000000760 15021222234 0021305 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Instantiator; use Doctrine\Instantiator\Exception\ExceptionInterface; /** * Instantiator provides utility methods to build objects without invoking their constructors */ interface InstantiatorInterface { /** * @phpstan-param class-string<T> $className * * @phpstan-return T * * @throws ExceptionInterface * * @template T of object */ public function instantiate(string $className): object; } instantiator/README.md 0000644 00000003053 15021222234 0010534 0 ustar 00 # Instantiator This library provides a way of avoiding usage of constructors when instantiating PHP classes. [](https://travis-ci.org/doctrine/instantiator) [](https://codecov.io/gh/doctrine/instantiator/branch/master) [](https://www.versioneye.com/package/php--doctrine--instantiator) [](https://packagist.org/packages/doctrine/instantiator) [](https://packagist.org/packages/doctrine/instantiator) ## Installation The suggested installation method is via [composer](https://getcomposer.org/): ```sh composer require doctrine/instantiator ``` ## Usage The instantiator is able to create new instances of any class without using the constructor or any API of the class itself: ```php $instantiator = new \Doctrine\Instantiator\Instantiator(); $instance = $instantiator->instantiate(\My\ClassName\Here::class); ``` ## Contributing Please read the [CONTRIBUTING.md](CONTRIBUTING.md) contents if you wish to help out! ## Credits This library was migrated from [ocramius/instantiator](https://github.com/Ocramius/Instantiator), which has been donated to the doctrine organization, and which is now deprecated in favour of this package. instantiator/LICENSE 0000644 00000002044 15021222234 0010261 0 ustar 00 Copyright (c) 2014 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. event-manager/UPGRADE.md 0000644 00000001144 15021222234 0010677 0 ustar 00 # Upgrade to 2.0 ## Made the `$event` parameter of `EventManager::getListeners()` mandatory When calling `EventManager::getListeners()` you need to specify the event that you want to fetch the listeners for. Call `getAllListeners()` instead if you want to access the listeners of all events. # Upgrade to 1.2 ## Deprecated calling `EventManager::getListeners()` without an event name When calling `EventManager::getListeners()` without an event name, all listeners were returned, keyed by event name. A new method `getAllListeners()` has been added to provide this functionality. It should be used instead. event-manager/composer.json 0000644 00000003277 15021222234 0012021 0 ustar 00 { "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 } } event-manager/psalm-baseline.xml 0000644 00000000450 15021222234 0012703 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <files psalm-version="5.24.0@462c80e31c34e58cc4f750c656be3927e80e550e"> <file src="src/EventManager.php"> <RiskyTruthyFalsyComparison> <code><![CDATA[empty($this->listeners[$event])]]></code> </RiskyTruthyFalsyComparison> </file> </files> event-manager/src/EventManager.php 0000644 00000007370 15021222234 0013151 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common; use function spl_object_hash; /** * The EventManager is the central point of Doctrine's event listener system. * Listeners are registered on the manager and events are dispatched through the * manager. */ class EventManager { /** * Map of registered listeners. * <event> => <listeners> * * @var array<string, object[]> */ private array $listeners = []; /** * Dispatches an event to all registered listeners. * * @param string $eventName The name of the event to dispatch. The name of the event is * the name of the method that is invoked on listeners. * @param EventArgs|null $eventArgs The event arguments to pass to the event handlers/listeners. * If not supplied, the single empty EventArgs instance is used. */ public function dispatchEvent(string $eventName, EventArgs|null $eventArgs = null): void { if (! isset($this->listeners[$eventName])) { return; } $eventArgs ??= EventArgs::getEmptyInstance(); foreach ($this->listeners[$eventName] as $listener) { $listener->$eventName($eventArgs); } } /** * Gets the listeners of a specific event. * * @param string $event The name of the event. * * @return object[] */ public function getListeners(string $event): array { return $this->listeners[$event] ?? []; } /** * Gets all listeners keyed by event name. * * @return array<string, object[]> The event listeners for the specified event, or all event listeners. */ public function getAllListeners(): array { return $this->listeners; } /** * Checks whether an event has any registered listeners. */ public function hasListeners(string $event): bool { return ! empty($this->listeners[$event]); } /** * Adds an event listener that listens on the specified events. * * @param string|string[] $events The event(s) to listen on. * @param object $listener The listener object. */ public function addEventListener(string|array $events, object $listener): void { // Picks the hash code related to that listener $hash = spl_object_hash($listener); foreach ((array) $events as $event) { // Overrides listener if a previous one was associated already // Prevents duplicate listeners on same event (same instance only) $this->listeners[$event][$hash] = $listener; } } /** * Removes an event listener from the specified events. * * @param string|string[] $events */ public function removeEventListener(string|array $events, object $listener): void { // Picks the hash code related to that listener $hash = spl_object_hash($listener); foreach ((array) $events as $event) { unset($this->listeners[$event][$hash]); } } /** * Adds an EventSubscriber. * * The subscriber is asked for all the events it is interested in and added * as a listener for these events. */ public function addEventSubscriber(EventSubscriber $subscriber): void { $this->addEventListener($subscriber->getSubscribedEvents(), $subscriber); } /** * Removes an EventSubscriber. * * The subscriber is asked for all the events it is interested in and removed * as a listener for these events. */ public function removeEventSubscriber(EventSubscriber $subscriber): void { $this->removeEventListener($subscriber->getSubscribedEvents(), $subscriber); } } event-manager/src/EventArgs.php 0000644 00000002242 15021222234 0012464 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common; /** * EventArgs is the base class for classes containing event data. * * This class contains no event data. It is used by events that do not pass state * information to an event handler when an event is raised. The single empty EventArgs * instance can be obtained through {@link getEmptyInstance}. */ class EventArgs { /** * Single instance of EventArgs. */ private static EventArgs|null $emptyEventArgsInstance = null; /** * Gets the single, empty and immutable EventArgs instance. * * This instance will be used when events are dispatched without any parameter, * like this: EventManager::dispatchEvent('eventname'); * * The benefit from this is that only one empty instance is instantiated and shared * (otherwise there would be instances for every dispatched in the abovementioned form). * * @link https://msdn.microsoft.com/en-us/library/system.eventargs.aspx * @see EventManager::dispatchEvent */ public static function getEmptyInstance(): EventArgs { return self::$emptyEventArgsInstance ??= new EventArgs(); } } event-manager/src/EventSubscriber.php 0000644 00000000760 15021222234 0013676 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common; /** * An EventSubscriber knows what events it is interested in. * If an EventSubscriber is added to an EventManager, the manager invokes * {@link getSubscribedEvents} and registers the subscriber as a listener for all * returned events. */ interface EventSubscriber { /** * Returns an array of events this subscriber wants to listen to. * * @return string[] */ public function getSubscribedEvents(); } event-manager/README.md 0000644 00000001510 15021222234 0010542 0 ustar 00 # Doctrine Event Manager [](https://github.com/doctrine/event-manager/actions) [](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=1.2.x) [](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=1.2.x) The Doctrine Event Manager is a library that provides a simple event system. ## More resources: * [Website](https://www.doctrine-project.org/) * [Documentation](https://www.doctrine-project.org/projects/doctrine-event-manager/en/latest/) * [Downloads](https://github.com/doctrine/event-manager/releases) event-manager/LICENSE 0000644 00000002051 15021222234 0010271 0 ustar 00 Copyright (c) 2006-2015 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dbal/bin/doctrine-dbal 0000755 00000000102 15021222234 0010635 0 ustar 00 #!/usr/bin/env php <?php require __DIR__ . '/doctrine-dbal.php'; dbal/bin/doctrine-dbal.php 0000644 00000002400 15021222234 0011423 0 ustar 00 <?php use Doctrine\DBAL\Tools\Console\ConsoleRunner; fwrite( STDERR, '[Warning] The use of this script is discouraged.' . ' You find instructions on how to bootstrap the console runner in our documentation.' . PHP_EOL, ); echo PHP_EOL . PHP_EOL; $files = [__DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../autoload.php']; $loader = null; $cwd = getcwd(); $directories = [$cwd, $cwd . DIRECTORY_SEPARATOR . 'config']; $configFile = null; foreach ($files as $file) { if (file_exists($file)) { $loader = require $file; break; } } if (! $loader) { throw new RuntimeException('vendor/autoload.php could not be found. Did you run `php composer.phar install`?'); } foreach ($directories as $directory) { $configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php'; if (file_exists($configFile)) { break; } } if (! file_exists($configFile)) { ConsoleRunner::printCliConfigTemplate(); exit(1); } if (! is_readable($configFile)) { echo 'Configuration file [' . $configFile . '] does not have read permission.' . PHP_EOL; exit(1); } $commands = []; $connectionProvider = require $configFile; ConsoleRunner::run($connectionProvider, $commands); dbal/composer.json 0000644 00000004363 15021222234 0010167 0 ustar 00 { "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.0", "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" } } } dbal/CONTRIBUTING.md 0000644 00000000556 15021222234 0007676 0 ustar 00 This repository has [guidelines specific to testing][testing guidelines], and Doctrine has [general contributing guidelines][contributor workflow], make sure you follow both. [contributor workflow]: https://www.doctrine-project.org/contribute/index.html [testing guidelines]: https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/testing.html dbal/src/Tools/Console/ConnectionNotFound.php 0000644 00000000212 15021222234 0015210 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console; use OutOfBoundsException; final class ConnectionNotFound extends OutOfBoundsException { } dbal/src/Tools/Console/Command/CommandCompatibility.php 0000644 00000001550 15021222234 0017130 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Tools\Console\Command; use ReflectionMethod; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; if ((new ReflectionMethod(Command::class, 'execute'))->hasReturnType()) { /** @internal */ trait CommandCompatibility { protected function execute(InputInterface $input, OutputInterface $output): int { return $this->doExecute($input, $output); } } } else { /** @internal */ trait CommandCompatibility { /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { return $this->doExecute($input, $output); } } } dbal/src/Tools/Console/Command/RunSqlCommand.php 0000644 00000007323 15021222234 0015547 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console\Command; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use RuntimeException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function array_keys; use function assert; use function is_bool; use function is_string; use function sprintf; use function stripos; /** * Task for executing arbitrary SQL that can come from a file or directly from * the command line. */ class RunSqlCommand extends Command { use CommandCompatibility; private ConnectionProvider $connectionProvider; public function __construct(ConnectionProvider $connectionProvider) { parent::__construct(); $this->connectionProvider = $connectionProvider; } /** @return void */ protected function configure() { $this ->setName('dbal:run-sql') ->setDescription('Executes arbitrary SQL directly from the command line.') ->setDefinition([ new InputOption('connection', null, InputOption::VALUE_REQUIRED, 'The named database connection'), new InputArgument('sql', InputArgument::REQUIRED, 'The SQL statement to execute.'), new InputOption('depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of result set (deprecated).'), new InputOption('force-fetch', null, InputOption::VALUE_NONE, 'Forces fetching the result.'), ]) ->setHelp(<<<'EOT' The <info>%command.name%</info> command executes the given SQL query and outputs the results: <info>php %command.full_name% "SELECT * FROM users"</info> EOT); } /** @throws Exception */ private function doExecute(InputInterface $input, OutputInterface $output): int { $conn = $this->getConnection($input); $io = new SymfonyStyle($input, $output); $sql = $input->getArgument('sql'); if ($sql === null) { throw new RuntimeException("Argument 'SQL' is required in order to execute this command correctly."); } assert(is_string($sql)); if ($input->getOption('depth') !== null) { $io->warning('Parameter "depth" is deprecated and has no effect anymore.'); } $forceFetch = $input->getOption('force-fetch'); assert(is_bool($forceFetch)); if (stripos($sql, 'select') === 0 || $forceFetch) { $this->runQuery($io, $conn, $sql); } else { $this->runStatement($io, $conn, $sql); } 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(); } /** @throws Exception */ private function runQuery(SymfonyStyle $io, Connection $conn, string $sql): void { $resultSet = $conn->fetchAllAssociative($sql); if ($resultSet === []) { $io->success('The query yielded an empty result set.'); return; } $io->table(array_keys($resultSet[0]), $resultSet); } /** @throws Exception */ private function runStatement(SymfonyStyle $io, Connection $conn, string $sql): void { $io->success(sprintf('%d rows affected.', $conn->executeStatement($sql))); } } dbal/src/Tools/Console/Command/ReservedWordsCommand.php 0000644 00000015676 15021222234 0017133 0 ustar 00 <?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(); } } dbal/src/Tools/Console/ConnectionProvider.php 0000644 00000000504 15021222234 0015252 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console; use Doctrine\DBAL\Connection; interface ConnectionProvider { public function getDefaultConnection(): Connection; /** @throws ConnectionNotFound in case a connection with the given name does not exist. */ public function getConnection(string $name): Connection; } dbal/src/Tools/Console/ConnectionProvider/SingleConnectionProvider.php 0000644 00000001715 15021222234 0022233 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console\ConnectionProvider; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Tools\Console\ConnectionNotFound; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use function sprintf; class SingleConnectionProvider implements ConnectionProvider { private Connection $connection; private string $defaultConnectionName; public function __construct(Connection $connection, string $defaultConnectionName = 'default') { $this->connection = $connection; $this->defaultConnectionName = $defaultConnectionName; } public function getDefaultConnection(): Connection { return $this->connection; } public function getConnection(string $name): Connection { if ($name !== $this->defaultConnectionName) { throw new ConnectionNotFound(sprintf('Connection with name "%s" does not exist.', $name)); } return $this->connection; } } dbal/src/Tools/Console/ConsoleRunner.php 0000644 00000004622 15021222234 0014241 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console; use Composer\InstalledVersions; use Doctrine\DBAL\Tools\Console\Command\ReservedWordsCommand; use Doctrine\DBAL\Tools\Console\Command\RunSqlCommand; use Exception; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use function assert; /** * Handles running the Console Tools inside Symfony Console context. * * @deprecated Use Symfony Console documentation to bootstrap a command-line application. */ class ConsoleRunner { /** * Runs console with the given connection provider. * * @param Command[] $commands * * @return void * * @throws Exception */ public static function run(ConnectionProvider $connectionProvider, $commands = []) { $version = InstalledVersions::getVersion('doctrine/dbal'); assert($version !== null); $cli = new Application('Doctrine Command Line Interface', $version); $cli->setCatchExceptions(true); self::addCommands($cli, $connectionProvider); $cli->addCommands($commands); $cli->run(); } /** @return void */ public static function addCommands(Application $cli, ConnectionProvider $connectionProvider) { $cli->addCommands([ new RunSqlCommand($connectionProvider), new ReservedWordsCommand($connectionProvider), ]); } /** * Prints the instructions to create a configuration file * * @deprecated This method will be removed without replacement. * * @return void */ public static function printCliConfigTemplate() { echo <<<'HELP' You are missing a "cli-config.php" or "config/cli-config.php" file in your project, which is required to get the Doctrine-DBAL Console working. You can use the following sample as a template: <?php use Doctrine\DBAL\Tools\Console\ConnectionProvider\SingleConnectionProvider; // You can append new commands to $commands array, if needed // replace with the mechanism to retrieve DBAL connection(s) in your app // and return a Doctrine\DBAL\Tools\Console\ConnectionProvider instance. $connection = getDBALConnection(); // in case you have a single connection you can use SingleConnectionProvider // otherwise you need to implement the Doctrine\DBAL\Tools\Console\ConnectionProvider interface with your custom logic return new SingleConnectionProvider($connection); HELP; } } dbal/src/Tools/DsnParser.php 0000644 00000015115 15021222234 0011743 0 ustar 00 <?php namespace Doctrine\DBAL\Tools; use Doctrine\DBAL\Driver; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception\MalformedDsnException; use SensitiveParameter; use function array_merge; use function assert; use function is_a; use function is_string; use function parse_str; use function parse_url; use function preg_replace; use function rawurldecode; use function str_replace; use function strpos; use function substr; /** @psalm-import-type Params from DriverManager */ final class DsnParser { /** @var array<string, string|class-string<Driver>> */ private array $schemeMapping; /** @param array<string, string|class-string<Driver>> $schemeMapping An array used to map DSN schemes to DBAL drivers */ public function __construct(array $schemeMapping = []) { $this->schemeMapping = $schemeMapping; } /** * @psalm-return Params * * @throws MalformedDsnException */ public function parse( #[SensitiveParameter] string $dsn ): array { // (pdo-)?sqlite3?:///... => (pdo-)?sqlite3?://localhost/... or else the URL will be invalid $url = preg_replace('#^((?:pdo-)?sqlite3?):///#', '$1://localhost/', $dsn); assert($url !== null); $url = parse_url($url); if ($url === false) { throw MalformedDsnException::new(); } foreach ($url as $param => $value) { if (! is_string($value)) { continue; } $url[$param] = rawurldecode($value); } $params = []; if (isset($url['scheme'])) { $params['driver'] = $this->parseDatabaseUrlScheme($url['scheme']); } if (isset($url['host'])) { $params['host'] = $url['host']; } if (isset($url['port'])) { $params['port'] = $url['port']; } if (isset($url['user'])) { $params['user'] = $url['user']; } if (isset($url['pass'])) { $params['password'] = $url['pass']; } if (isset($params['driver']) && is_a($params['driver'], Driver::class, true)) { $params['driverClass'] = $params['driver']; unset($params['driver']); } $params = $this->parseDatabaseUrlPath($url, $params); $params = $this->parseDatabaseUrlQuery($url, $params); return $params; } /** * Parses the given connection URL and resolves the given connection parameters. * * Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters * via {@see parseDatabaseUrlScheme}. * * @see parseDatabaseUrlScheme * * @param mixed[] $url The URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private function parseDatabaseUrlPath(array $url, array $params): array { if (! isset($url['path'])) { return $params; } $url['path'] = $this->normalizeDatabaseUrlPath($url['path']); // If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate // and therefore treat the path as a regular DBAL connection URL path. if (! isset($params['driver'])) { return $this->parseRegularDatabaseUrlPath($url, $params); } if (strpos($params['driver'], 'sqlite') !== false) { return $this->parseSqliteDatabaseUrlPath($url, $params); } return $this->parseRegularDatabaseUrlPath($url, $params); } /** * Normalizes the given connection URL path. * * @return string The normalized connection URL path */ private function normalizeDatabaseUrlPath(string $urlPath): string { // Trim leading slash from URL path. return substr($urlPath, 1); } /** * Parses the query part of the given connection URL and resolves the given connection parameters. * * @param mixed[] $url The connection URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private function parseDatabaseUrlQuery(array $url, array $params): array { if (! isset($url['query'])) { return $params; } $query = []; parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode return array_merge($params, $query); // parse_str wipes existing array elements } /** * Parses the given regular connection URL and resolves the given connection parameters. * * Assumes that the "path" URL part is already normalized via {@see normalizeDatabaseUrlPath}. * * @see normalizeDatabaseUrlPath * * @param mixed[] $url The regular connection URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private function parseRegularDatabaseUrlPath(array $url, array $params): array { $params['dbname'] = $url['path']; return $params; } /** * Parses the given SQLite connection URL and resolves the given connection parameters. * * Assumes that the "path" URL part is already normalized via {@see normalizeDatabaseUrlPath}. * * @see normalizeDatabaseUrlPath * * @param mixed[] $url The SQLite connection URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private function parseSqliteDatabaseUrlPath(array $url, array $params): array { if ($url['path'] === ':memory:') { $params['memory'] = true; return $params; } $params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key return $params; } /** * Parses the scheme part from given connection URL and resolves the given connection parameters. * * @return string The resolved driver. */ private function parseDatabaseUrlScheme(string $scheme): string { // URL schemes must not contain underscores, but dashes are ok $driver = str_replace('-', '_', $scheme); // If the driver is an alias (e.g. "postgres"), map it to the actual name ("pdo-pgsql"). // Otherwise, let checkParams decide later if the driver exists. return $this->schemeMapping[$driver] ?? $driver; } } dbal/src/Connections/PrimaryReadReplicaConnection.php 0000644 00000025634 15021222234 0016772 0 ustar 00 <?php namespace Doctrine\DBAL\Connections; use Doctrine\Common\EventManager; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection as DriverConnection; use Doctrine\DBAL\Driver\Exception as DriverException; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Statement; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use SensitiveParameter; use function array_rand; use function count; /** * Primary-Replica Connection * * Connection can be used with primary-replica setups. * * Important for the understanding of this connection should be how and when * it picks the replica or primary. * * 1. Replica if primary was never picked before and ONLY if 'getWrappedConnection' * or 'executeQuery' is used. * 2. Primary picked when 'executeStatement', 'insert', 'delete', 'update', 'createSavepoint', * 'releaseSavepoint', 'beginTransaction', 'rollback', 'commit' or 'prepare' is called. * 3. If Primary was picked once during the lifetime of the connection it will always get picked afterwards. * 4. One replica connection is randomly picked ONCE during a request. * * ATTENTION: You can write to the replica with this connection if you execute a write query without * opening up a transaction. For example: * * $conn = DriverManager::getConnection(...); * $conn->executeQuery("DELETE FROM table"); * * Be aware that Connection#executeQuery is a method specifically for READ * operations only. * * Use Connection#executeStatement for any SQL statement that changes/updates * state in the database (UPDATE, INSERT, DELETE or DDL statements). * * This connection is limited to replica operations using the * Connection#executeQuery operation only, because it wouldn't be compatible * with the ORM or SchemaManager code otherwise. Both use all the other * operations in a context where writes could happen to a replica, which makes * this restricted approach necessary. * * You can manually connect to the primary at any time by calling: * * $conn->ensureConnectedToPrimary(); * * Instantiation through the DriverManager looks like: * * @psalm-import-type Params from DriverManager * @example * * $conn = DriverManager::getConnection(array( * 'wrapperClass' => 'Doctrine\DBAL\Connections\PrimaryReadReplicaConnection', * 'driver' => 'pdo_mysql', * 'primary' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''), * 'replica' => array( * array('user' => 'replica1', 'password' => '', 'host' => '', 'dbname' => ''), * array('user' => 'replica2', 'password' => '', 'host' => '', 'dbname' => ''), * ) * )); * * You can also pass 'driverOptions' and any other documented option to each of this drivers * to pass additional information. */ class PrimaryReadReplicaConnection extends Connection { /** * Primary and Replica connection (one of the randomly picked replicas). * * @var DriverConnection[]|null[] */ protected $connections = ['primary' => null, 'replica' => null]; /** * You can keep the replica connection and then switch back to it * during the request if you know what you are doing. * * @var bool */ protected $keepReplica = false; /** * Creates Primary Replica Connection. * * @internal The connection can be only instantiated by the driver manager. * * @param array<string,mixed> $params * @psalm-param Params $params * * @throws Exception * @throws InvalidArgumentException */ public function __construct( array $params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null ) { if (! isset($params['replica'], $params['primary'])) { throw new InvalidArgumentException('primary or replica configuration missing'); } if (count($params['replica']) === 0) { throw new InvalidArgumentException('You have to configure at least one replica.'); } if (isset($params['driver'])) { $params['primary']['driver'] = $params['driver']; foreach ($params['replica'] as $replicaKey => $replica) { $params['replica'][$replicaKey]['driver'] = $params['driver']; } } $this->keepReplica = (bool) ($params['keepReplica'] ?? false); parent::__construct($params, $driver, $config, $eventManager); } /** * Checks if the connection is currently towards the primary or not. */ public function isConnectedToPrimary(): bool { return $this->_conn !== null && $this->_conn === $this->connections['primary']; } /** * @param string|null $connectionName * * @return bool */ public function connect($connectionName = null) { if ($connectionName !== null) { throw new InvalidArgumentException( 'Passing a connection name as first argument is not supported anymore.' . ' Use ensureConnectedToPrimary()/ensureConnectedToReplica() instead.', ); } return $this->performConnect(); } protected function performConnect(?string $connectionName = null): bool { $requestedConnectionChange = ($connectionName !== null); $connectionName = $connectionName ?? 'replica'; if ($connectionName !== 'replica' && $connectionName !== 'primary') { throw new InvalidArgumentException('Invalid option to connect(), only primary or replica allowed.'); } // If we have a connection open, and this is not an explicit connection // change request, then abort right here, because we are already done. // This prevents writes to the replica in case of "keepReplica" option enabled. if ($this->_conn !== null && ! $requestedConnectionChange) { return false; } $forcePrimaryAsReplica = false; if ($this->getTransactionNestingLevel() > 0) { $connectionName = 'primary'; $forcePrimaryAsReplica = true; } if (isset($this->connections[$connectionName])) { $this->_conn = $this->connections[$connectionName]; if ($forcePrimaryAsReplica && ! $this->keepReplica) { $this->connections['replica'] = $this->_conn; } return false; } if ($connectionName === 'primary') { $this->connections['primary'] = $this->_conn = $this->connectTo($connectionName); // Set replica connection to primary to avoid invalid reads if (! $this->keepReplica) { $this->connections['replica'] = $this->connections['primary']; } } else { $this->connections['replica'] = $this->_conn = $this->connectTo($connectionName); } if ($this->_eventManager->hasListeners(Events::postConnect)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated. Implement a middleware instead.', Events::postConnect, ); $eventArgs = new ConnectionEventArgs($this); $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); } return true; } /** * Connects to the primary node of the database cluster. * * All following statements after this will be executed against the primary node. */ public function ensureConnectedToPrimary(): bool { return $this->performConnect('primary'); } /** * Connects to a replica node of the database cluster. * * All following statements after this will be executed against the replica node, * unless the keepReplica option is set to false and a primary connection * was already opened. */ public function ensureConnectedToReplica(): bool { return $this->performConnect('replica'); } /** * Connects to a specific connection. * * @param string $connectionName * * @return DriverConnection * * @throws Exception */ protected function connectTo($connectionName) { $params = $this->getParams(); $connectionParams = $this->chooseConnectionConfiguration($connectionName, $params); try { return $this->_driver->connect($connectionParams); } catch (DriverException $e) { throw $this->convertException($e); } } /** * @param string $connectionName * @param mixed[] $params * * @return mixed */ protected function chooseConnectionConfiguration( $connectionName, #[SensitiveParameter] $params ) { if ($connectionName === 'primary') { return $params['primary']; } $config = $params['replica'][array_rand($params['replica'])]; if (! isset($config['charset']) && isset($params['primary']['charset'])) { $config['charset'] = $params['primary']['charset']; } return $config; } /** * {@inheritDoc} */ public function executeStatement($sql, array $params = [], array $types = []) { $this->ensureConnectedToPrimary(); return parent::executeStatement($sql, $params, $types); } /** * {@inheritDoc} */ public function beginTransaction() { $this->ensureConnectedToPrimary(); return parent::beginTransaction(); } /** * {@inheritDoc} */ public function commit() { $this->ensureConnectedToPrimary(); return parent::commit(); } /** * {@inheritDoc} */ public function rollBack() { $this->ensureConnectedToPrimary(); return parent::rollBack(); } /** * {@inheritDoc} */ public function close() { unset($this->connections['primary'], $this->connections['replica']); parent::close(); $this->_conn = null; $this->connections = ['primary' => null, 'replica' => null]; } /** * {@inheritDoc} */ public function createSavepoint($savepoint) { $this->ensureConnectedToPrimary(); parent::createSavepoint($savepoint); } /** * {@inheritDoc} */ public function releaseSavepoint($savepoint) { $this->ensureConnectedToPrimary(); parent::releaseSavepoint($savepoint); } /** * {@inheritDoc} */ public function rollbackSavepoint($savepoint) { $this->ensureConnectedToPrimary(); parent::rollbackSavepoint($savepoint); } public function prepare(string $sql): Statement { $this->ensureConnectedToPrimary(); return parent::prepare($sql); } } dbal/src/ColumnCase.php 0000644 00000000655 15021222234 0010776 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Contains portable column case conversions. */ final class ColumnCase { /** * Convert column names to upper case. */ public const UPPER = 1; /** * Convert column names to lower case. */ public const LOWER = 2; /** * This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } } dbal/src/Query.php 0000644 00000002247 15021222234 0010051 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL; use Doctrine\DBAL\Types\Type; /** * An SQL query together with its bound parameters. * * @psalm-immutable */ final class Query { /** * The SQL query. */ private string $sql; /** * The parameters bound to the query. * * @var array<mixed> */ private array $params; /** * The types of the parameters bound to the query. * * @var array<Type|int|string|null> */ private array $types; /** * @param array<mixed> $params * @param array<Type|int|string|null> $types * * @psalm-suppress ImpurePropertyAssignment */ public function __construct(string $sql, array $params, array $types) { $this->sql = $sql; $this->params = $params; $this->types = $types; } public function getSQL(): string { return $this->sql; } /** @return array<mixed> */ public function getParams(): array { return $this->params; } /** @return array<Type|int|string|null> */ public function getTypes(): array { return $this->types; } } dbal/src/LockMode.php 0000644 00000000643 15021222234 0010437 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Contains all DBAL LockModes. */ class LockMode { public const NONE = 0; public const OPTIMISTIC = 1; public const PESSIMISTIC_READ = 2; public const PESSIMISTIC_WRITE = 4; /** * Private constructor. This class cannot be instantiated. * * @codeCoverageIgnore */ final private function __construct() { } } dbal/src/VersionAwarePlatformDriver.php 0000644 00000002027 15021222234 0014226 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Contract for a driver that is able to create platform instances by version. * * Doctrine uses different platform classes for different vendor versions to * support the correct features and SQL syntax of each version. * This interface should be implemented by drivers that are capable to do this * distinction. * * @deprecated All drivers will have to be aware of the server version in the next major release. */ interface VersionAwarePlatformDriver extends Driver { /** * Factory method for creating the appropriate platform instance for the given version. * * @param string $version The platform/server version string to evaluate. This should be given in the notation * the underlying database vendor uses. * * @return AbstractPlatform * * @throws Exception If the given version string could not be evaluated. */ public function createDatabasePlatformForVersion($version); } dbal/src/ExpandArrayParameters.php 0000644 00000007373 15021222234 0013213 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\ArrayParameters\Exception\MissingNamedParameter; use Doctrine\DBAL\ArrayParameters\Exception\MissingPositionalParameter; use Doctrine\DBAL\SQL\Parser\Visitor; use Doctrine\DBAL\Types\Type; use function array_fill; use function array_key_exists; use function count; use function implode; use function substr; final class ExpandArrayParameters implements Visitor { /** @var array<int,mixed>|array<string,mixed> */ private array $originalParameters; /** @var array<int,Type|int|string|null>|array<string,Type|int|string|null> */ private array $originalTypes; private int $originalParameterIndex = 0; /** @var list<string> */ private array $convertedSQL = []; /** @var list<mixed> */ private array $convertedParameters = []; /** @var array<int,Type|int|string|null> */ private array $convertedTypes = []; /** * @param array<int, mixed>|array<string, mixed> $parameters * @param array<int,Type|int|string|null>|array<string,Type|int|string|null> $types */ public function __construct(array $parameters, array $types) { $this->originalParameters = $parameters; $this->originalTypes = $types; } public function acceptPositionalParameter(string $sql): void { $index = $this->originalParameterIndex; if (! array_key_exists($index, $this->originalParameters)) { throw MissingPositionalParameter::new($index); } $this->acceptParameter($index, $this->originalParameters[$index]); $this->originalParameterIndex++; } public function acceptNamedParameter(string $sql): void { $name = substr($sql, 1); if (! array_key_exists($name, $this->originalParameters)) { throw MissingNamedParameter::new($name); } $this->acceptParameter($name, $this->originalParameters[$name]); } public function acceptOther(string $sql): void { $this->convertedSQL[] = $sql; } public function getSQL(): string { return implode('', $this->convertedSQL); } /** @return list<mixed> */ public function getParameters(): array { return $this->convertedParameters; } /** * @param int|string $key * @param mixed $value */ private function acceptParameter($key, $value): void { if (! isset($this->originalTypes[$key])) { $this->convertedSQL[] = '?'; $this->convertedParameters[] = $value; return; } $type = $this->originalTypes[$key]; if ( $type !== ArrayParameterType::INTEGER && $type !== ArrayParameterType::STRING && $type !== ArrayParameterType::ASCII && $type !== ArrayParameterType::BINARY ) { $this->appendTypedParameter([$value], $type); return; } if (count($value) === 0) { $this->convertedSQL[] = 'NULL'; return; } $this->appendTypedParameter($value, ArrayParameterType::toElementParameterType($type)); } /** @return array<int,Type|int|string|null> */ public function getTypes(): array { return $this->convertedTypes; } /** * @param list<mixed> $values * @param Type|int|string|null $type */ private function appendTypedParameter(array $values, $type): void { $this->convertedSQL[] = implode(', ', array_fill(0, count($values), '?')); $index = count($this->convertedParameters); foreach ($values as $value) { $this->convertedParameters[] = $value; $this->convertedTypes[$index] = $type; $index++; } } } dbal/src/Event/Listeners/SQLiteSessionInit.php 0000644 00000001272 15021222234 0015323 0 ustar 00 <?php namespace Doctrine\DBAL\Event\Listeners; use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; /** @deprecated Use {@see \Doctrine\DBAL\Driver\AbstractSQLiteDriver\Middleware\EnableForeignKeys} instead. */ class SQLiteSessionInit implements EventSubscriber { /** * @return void * * @throws Exception */ public function postConnect(ConnectionEventArgs $args) { $args->getConnection()->executeStatement('PRAGMA foreign_keys=ON'); } /** * {@inheritDoc} */ public function getSubscribedEvents() { return [Events::postConnect]; } } dbal/src/Event/Listeners/SQLSessionInit.php 0000644 00000001556 15021222234 0014626 0 ustar 00 <?php namespace Doctrine\DBAL\Event\Listeners; use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; /** * Session init listener for executing a single SQL statement right after a connection is opened. * * @deprecated Implement a middleware instead. */ class SQLSessionInit implements EventSubscriber { /** @var string */ protected $sql; /** @param string $sql */ public function __construct($sql) { $this->sql = $sql; } /** * @return void * * @throws Exception */ public function postConnect(ConnectionEventArgs $args) { $args->getConnection()->executeStatement($this->sql); } /** * {@inheritDoc} */ public function getSubscribedEvents() { return [Events::postConnect]; } } dbal/src/Event/Listeners/OracleSessionInit.php 0000644 00000004237 15021222234 0015373 0 ustar 00 <?php namespace Doctrine\DBAL\Event\Listeners; use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use function array_change_key_case; use function array_merge; use function count; use function implode; use const CASE_UPPER; /** * Should be used when Oracle Server default environment does not match the Doctrine requirements. * * The following environment variables are required for the Doctrine default date format: * * NLS_TIME_FORMAT="HH24:MI:SS" * NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS" * NLS_TIMESTAMP_FORMAT="YYYY-MM-DD HH24:MI:SS" * NLS_TIMESTAMP_TZ_FORMAT="YYYY-MM-DD HH24:MI:SS TZH:TZM" * * @deprecated Use {@see \Doctrine\DBAL\Driver\OCI8\Middleware\InitializeSession} instead. */ class OracleSessionInit implements EventSubscriber { /** @var string[] */ protected $_defaultSessionVars = [ 'NLS_TIME_FORMAT' => 'HH24:MI:SS', 'NLS_DATE_FORMAT' => 'YYYY-MM-DD 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' => '.,', ]; /** @param string[] $oracleSessionVars */ public function __construct(array $oracleSessionVars = []) { $this->_defaultSessionVars = array_merge($this->_defaultSessionVars, $oracleSessionVars); } /** * @return void * * @throws Exception */ public function postConnect(ConnectionEventArgs $args) { if (count($this->_defaultSessionVars) === 0) { return; } $vars = []; foreach (array_change_key_case($this->_defaultSessionVars, CASE_UPPER) as $option => $value) { if ($option === 'CURRENT_SCHEMA') { $vars[] = $option . ' = ' . $value; } else { $vars[] = $option . " = '" . $value . "'"; } } $sql = 'ALTER SESSION SET ' . implode(' ', $vars); $args->getConnection()->executeStatement($sql); } /** * {@inheritDoc} */ public function getSubscribedEvents() { return [Events::postConnect]; } } dbal/src/Event/SchemaAlterTableRenameColumnEventArgs.php 0000644 00000003606 15021222234 0017312 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for renaming table columns are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaAlterTableRenameColumnEventArgs extends SchemaEventArgs { /** @var string */ private $oldColumnName; private Column $column; private TableDiff $tableDiff; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; /** @param string $oldColumnName */ public function __construct($oldColumnName, Column $column, TableDiff $tableDiff, AbstractPlatform $platform) { $this->oldColumnName = $oldColumnName; $this->column = $column; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** @return string */ public function getOldColumnName() { return $this->oldColumnName; } /** @return Column */ public function getColumn() { return $this->column; } /** @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableRenameColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } dbal/src/Event/TransactionBeginEventArgs.php 0000644 00000000225 15021222234 0015070 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Event; /** @deprecated */ class TransactionBeginEventArgs extends TransactionEventArgs { } dbal/src/Event/SchemaAlterTableEventArgs.php 0000644 00000002567 15021222234 0015011 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for creating tables are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaAlterTableEventArgs extends SchemaEventArgs { private TableDiff $tableDiff; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; public function __construct(TableDiff $tableDiff, AbstractPlatform $platform) { $this->tableDiff = $tableDiff; $this->platform = $platform; } /** @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } dbal/src/Event/TransactionRollBackEventArgs.php 0000644 00000000230 15021222234 0015531 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Event; /** @deprecated */ class TransactionRollBackEventArgs extends TransactionEventArgs { } dbal/src/Event/TransactionEventArgs.php 0000644 00000000675 15021222234 0014134 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Event; use Doctrine\Common\EventArgs; use Doctrine\DBAL\Connection; /** @deprecated */ abstract class TransactionEventArgs extends EventArgs { private Connection $connection; public function __construct(Connection $connection) { $this->connection = $connection; } public function getConnection(): Connection { return $this->connection; } } dbal/src/Event/SchemaIndexDefinitionEventArgs.php 0000644 00000003011 15021222234 0016033 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Index; /** * Event Arguments used when the portable index definition is generated inside {@see AbstractSchemaManager}. * * @deprecated */ class SchemaIndexDefinitionEventArgs extends SchemaEventArgs { private ?Index $index = null; /** * Raw index data as fetched from the database. * * @var mixed[] */ private array $tableIndex; /** @var string */ private $table; private Connection $connection; /** * @param mixed[] $tableIndex * @param string $table */ public function __construct(array $tableIndex, $table, Connection $connection) { $this->tableIndex = $tableIndex; $this->table = $table; $this->connection = $connection; } /** * Allows to clear the index which means the index will be excluded from tables index list. * * @return SchemaIndexDefinitionEventArgs */ public function setIndex(?Index $index = null) { $this->index = $index; return $this; } /** @return Index|null */ public function getIndex() { return $this->index; } /** @return mixed[] */ public function getTableIndex() { return $this->tableIndex; } /** @return string */ public function getTable() { return $this->table; } /** @return Connection */ public function getConnection() { return $this->connection; } } dbal/src/Event/ConnectionEventArgs.php 0000644 00000001023 15021222234 0013732 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\Common\EventArgs; use Doctrine\DBAL\Connection; /** * Event Arguments used when a Driver connection is established inside Doctrine\DBAL\Connection. * * @deprecated */ class ConnectionEventArgs extends EventArgs { private Connection $connection; public function __construct(Connection $connection) { $this->connection = $connection; } /** @return Connection */ public function getConnection() { return $this->connection; } } dbal/src/Event/SchemaAlterTableChangeColumnEventArgs.php 0000644 00000003223 15021222234 0017263 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for changing table columns are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaAlterTableChangeColumnEventArgs extends SchemaEventArgs { private ColumnDiff $columnDiff; private TableDiff $tableDiff; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; public function __construct(ColumnDiff $columnDiff, TableDiff $tableDiff, AbstractPlatform $platform) { $this->columnDiff = $columnDiff; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** @return ColumnDiff */ public function getColumnDiff() { return $this->columnDiff; } /** @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableChangeColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } dbal/src/Event/TransactionCommitEventArgs.php 0000644 00000000226 15021222234 0015275 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Event; /** @deprecated */ class TransactionCommitEventArgs extends TransactionEventArgs { } dbal/src/Event/SchemaCreateTableEventArgs.php 0000644 00000003514 15021222234 0015136 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Table; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for creating tables are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaCreateTableEventArgs extends SchemaEventArgs { private Table $table; /** @var mixed[][] */ private array $columns; /** @var mixed[] */ private array $options; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; /** * @param mixed[][] $columns * @param mixed[] $options */ public function __construct(Table $table, array $columns, array $options, AbstractPlatform $platform) { $this->table = $table; $this->columns = $columns; $this->options = $options; $this->platform = $platform; } /** @return Table */ public function getTable() { return $this->table; } /** @return mixed[][] */ public function getColumns() { return $this->columns; } /** @return mixed[] */ public function getOptions() { return $this->options; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaCreateTableEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } dbal/src/Event/SchemaDropTableEventArgs.php 0000644 00000002321 15021222234 0014632 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Table; use InvalidArgumentException; /** * Event Arguments used when the SQL query for dropping tables are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaDropTableEventArgs extends SchemaEventArgs { /** @var string|Table */ private $table; private AbstractPlatform $platform; /** @var string|null */ private $sql; /** * @param string|Table $table * * @throws InvalidArgumentException */ public function __construct($table, AbstractPlatform $platform) { $this->table = $table; $this->platform = $platform; } /** @return string|Table */ public function getTable() { return $this->table; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * @param string $sql * * @return SchemaDropTableEventArgs */ public function setSql($sql) { $this->sql = $sql; return $this; } /** @return string|null */ public function getSql() { return $this->sql; } } dbal/src/Event/SchemaCreateTableColumnEventArgs.php 0000644 00000003073 15021222234 0016314 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Table; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for creating table columns are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaCreateTableColumnEventArgs extends SchemaEventArgs { private Column $column; private Table $table; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; public function __construct(Column $column, Table $table, AbstractPlatform $platform) { $this->column = $column; $this->table = $table; $this->platform = $platform; } /** @return Column */ public function getColumn() { return $this->column; } /** @return Table */ public function getTable() { return $this->table; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaCreateTableColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } dbal/src/Event/SchemaEventArgs.php 0000644 00000000746 15021222234 0013046 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\Common\EventArgs; /** * Base class for schema related events. * * @deprecated */ class SchemaEventArgs extends EventArgs { private bool $preventDefault = false; /** @return SchemaEventArgs */ public function preventDefault() { $this->preventDefault = true; return $this; } /** @return bool */ public function isDefaultPrevented() { return $this->preventDefault; } } dbal/src/Event/SchemaAlterTableAddColumnEventArgs.php 0000644 00000004022 15021222234 0016564 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\Deprecations\Deprecation; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for adding table columns are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaAlterTableAddColumnEventArgs extends SchemaEventArgs { private Column $column; private TableDiff $tableDiff; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; public function __construct(Column $column, TableDiff $tableDiff, AbstractPlatform $platform) { $this->column = $column; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** @return Column */ public function getColumn() { return $this->column; } /** @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableAddColumnEventArgs */ public function addSql($sql) { if (is_array($sql)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Passing multiple SQL statements as an array to SchemaAlterTableAddColumnEventaArrgs::addSql() ' . 'is deprecated. Pass each statement as an individual argument instead.', ); } $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } dbal/src/Event/SchemaAlterTableRemoveColumnEventArgs.php 0000644 00000003154 15021222234 0017336 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for removing table columns are generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaAlterTableRemoveColumnEventArgs extends SchemaEventArgs { private Column $column; private TableDiff $tableDiff; private AbstractPlatform $platform; /** @var string[] */ private array $sql = []; public function __construct(Column $column, TableDiff $tableDiff, AbstractPlatform $platform) { $this->column = $column; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** @return Column */ public function getColumn() { return $this->column; } /** @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableRemoveColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** @return string[] */ public function getSql() { return $this->sql; } } dbal/src/Event/SchemaColumnDefinitionEventArgs.php 0000644 00000003413 15021222234 0016227 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Column; /** * Event Arguments used when the portable column definition is generated inside {@see AbstractPlatform}. * * @deprecated */ class SchemaColumnDefinitionEventArgs extends SchemaEventArgs { private ?Column $column = null; /** * Raw column data as fetched from the database. * * @var mixed[] */ private $tableColumn; /** @var string */ private $table; /** @var string */ private $database; private Connection $connection; /** * @param mixed[] $tableColumn * @param string $table * @param string $database */ public function __construct(array $tableColumn, $table, $database, Connection $connection) { $this->tableColumn = $tableColumn; $this->table = $table; $this->database = $database; $this->connection = $connection; } /** * Allows to clear the column which means the column will be excluded from * tables column list. * * @return SchemaColumnDefinitionEventArgs */ public function setColumn(?Column $column = null) { $this->column = $column; return $this; } /** @return Column|null */ public function getColumn() { return $this->column; } /** @return mixed[] */ public function getTableColumn() { return $this->tableColumn; } /** @return string */ public function getTable() { return $this->table; } /** @return string */ public function getDatabase() { return $this->database; } /** @return Connection */ public function getConnection() { return $this->connection; } } dbal/src/Events.php 0000644 00000003115 15021222234 0010203 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Container for all DBAL events. * * This class cannot be instantiated. * * @deprecated */ final class Events { /** * Private constructor. This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } /** @deprecated */ public const postConnect = 'postConnect'; /** @deprecated */ public const onSchemaCreateTable = 'onSchemaCreateTable'; /** @deprecated */ public const onSchemaCreateTableColumn = 'onSchemaCreateTableColumn'; /** @deprecated */ public const onSchemaDropTable = 'onSchemaDropTable'; /** @deprecated */ public const onSchemaAlterTable = 'onSchemaAlterTable'; /** @deprecated */ public const onSchemaAlterTableAddColumn = 'onSchemaAlterTableAddColumn'; /** @deprecated */ public const onSchemaAlterTableRemoveColumn = 'onSchemaAlterTableRemoveColumn'; /** @deprecated */ public const onSchemaAlterTableChangeColumn = 'onSchemaAlterTableChangeColumn'; /** @deprecated */ public const onSchemaAlterTableRenameColumn = 'onSchemaAlterTableRenameColumn'; /** @deprecated */ public const onSchemaColumnDefinition = 'onSchemaColumnDefinition'; /** @deprecated */ public const onSchemaIndexDefinition = 'onSchemaIndexDefinition'; /** @deprecated */ public const onTransactionBegin = 'onTransactionBegin'; /** @deprecated */ public const onTransactionCommit = 'onTransactionCommit'; /** @deprecated */ public const onTransactionRollBack = 'onTransactionRollBack'; } dbal/src/Schema/Column.php 0000644 00000022616 15021222234 0011403 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Schema\Exception\UnknownColumnOption; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_merge; use function is_numeric; use function method_exists; /** * Object representation of a database column. */ class Column extends AbstractAsset { /** @var Type */ protected $_type; /** @var int|null */ protected $_length; /** @var int */ protected $_precision = 10; /** @var int */ protected $_scale = 0; /** @var bool */ protected $_unsigned = false; /** @var bool */ protected $_fixed = false; /** @var bool */ protected $_notnull = true; /** @var mixed */ protected $_default; /** @var bool */ protected $_autoincrement = false; /** @var mixed[] */ protected $_platformOptions = []; /** @var string|null */ protected $_columnDefinition; /** @var string|null */ protected $_comment; /** * @deprecated Use {@link $_platformOptions} instead * * @var mixed[] */ protected $_customSchemaOptions = []; /** * Creates a new Column. * * @param string $name * @param mixed[] $options * * @throws SchemaException */ public function __construct($name, Type $type, array $options = []) { $this->_setName($name); $this->setType($type); $this->setOptions($options); } /** * @param mixed[] $options * * @return Column * * @throws SchemaException */ public function setOptions(array $options) { foreach ($options as $name => $value) { $method = 'set' . $name; if (! method_exists($this, $method)) { throw UnknownColumnOption::new($name); } $this->$method($value); } return $this; } /** @return Column */ public function setType(Type $type) { $this->_type = $type; return $this; } /** * @param int|null $length * * @return Column */ public function setLength($length) { if ($length !== null) { $this->_length = (int) $length; } else { $this->_length = null; } return $this; } /** * @param int $precision * * @return Column */ public function setPrecision($precision) { if (! is_numeric($precision)) { $precision = 10; // defaults to 10 when no valid precision is given. } $this->_precision = (int) $precision; return $this; } /** * @param int $scale * * @return Column */ public function setScale($scale) { if (! is_numeric($scale)) { $scale = 0; } $this->_scale = (int) $scale; return $this; } /** * @param bool $unsigned * * @return Column */ public function setUnsigned($unsigned) { $this->_unsigned = (bool) $unsigned; return $this; } /** * @param bool $fixed * * @return Column */ public function setFixed($fixed) { $this->_fixed = (bool) $fixed; return $this; } /** * @param bool $notnull * * @return Column */ public function setNotnull($notnull) { $this->_notnull = (bool) $notnull; return $this; } /** * @param mixed $default * * @return Column */ public function setDefault($default) { $this->_default = $default; return $this; } /** * @param mixed[] $platformOptions * * @return Column */ public function setPlatformOptions(array $platformOptions) { $this->_platformOptions = $platformOptions; return $this; } /** * @param string $name * @param mixed $value * * @return Column */ public function setPlatformOption($name, $value) { $this->_platformOptions[$name] = $value; return $this; } /** * @param string|null $value * * @return Column */ public function setColumnDefinition($value) { $this->_columnDefinition = $value; return $this; } /** @return Type */ public function getType() { return $this->_type; } /** @return int|null */ public function getLength() { return $this->_length; } /** @return int */ public function getPrecision() { return $this->_precision; } /** @return int */ public function getScale() { return $this->_scale; } /** @return bool */ public function getUnsigned() { return $this->_unsigned; } /** @return bool */ public function getFixed() { return $this->_fixed; } /** @return bool */ public function getNotnull() { return $this->_notnull; } /** @return mixed */ public function getDefault() { return $this->_default; } /** @return mixed[] */ public function getPlatformOptions() { return $this->_platformOptions; } /** * @param string $name * * @return bool */ public function hasPlatformOption($name) { return isset($this->_platformOptions[$name]); } /** * @param string $name * * @return mixed */ public function getPlatformOption($name) { return $this->_platformOptions[$name]; } /** @return string|null */ public function getColumnDefinition() { return $this->_columnDefinition; } /** @return bool */ public function getAutoincrement() { return $this->_autoincrement; } /** * @param bool $flag * * @return Column */ public function setAutoincrement($flag) { $this->_autoincrement = $flag; return $this; } /** * @param string|null $comment * * @return Column */ public function setComment($comment) { $this->_comment = $comment; return $this; } /** @return string|null */ public function getComment() { return $this->_comment; } /** * @deprecated Use {@link setPlatformOption()} instead * * @param string $name * @param mixed $value * * @return Column */ public function setCustomSchemaOption($name, $value) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5476', 'Column::setCustomSchemaOption() is deprecated. Use setPlatformOption() instead.', ); $this->_customSchemaOptions[$name] = $value; return $this; } /** * @deprecated Use {@link hasPlatformOption()} instead * * @param string $name * * @return bool */ public function hasCustomSchemaOption($name) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5476', 'Column::hasCustomSchemaOption() is deprecated. Use hasPlatformOption() instead.', ); return isset($this->_customSchemaOptions[$name]); } /** * @deprecated Use {@link getPlatformOption()} instead * * @param string $name * * @return mixed */ public function getCustomSchemaOption($name) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5476', 'Column::getCustomSchemaOption() is deprecated. Use getPlatformOption() instead.', ); return $this->_customSchemaOptions[$name]; } /** * @deprecated Use {@link setPlatformOptions()} instead * * @param mixed[] $customSchemaOptions * * @return Column */ public function setCustomSchemaOptions(array $customSchemaOptions) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5476', 'Column::setCustomSchemaOptions() is deprecated. Use setPlatformOptions() instead.', ); $this->_customSchemaOptions = $customSchemaOptions; return $this; } /** * @deprecated Use {@link getPlatformOptions()} instead * * @return mixed[] */ public function getCustomSchemaOptions() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5476', 'Column::getCustomSchemaOptions() is deprecated. Use getPlatformOptions() instead.', ); return $this->_customSchemaOptions; } /** @return mixed[] */ public function toArray() { return array_merge([ 'name' => $this->_name, 'type' => $this->_type, 'default' => $this->_default, 'notnull' => $this->_notnull, 'length' => $this->_length, 'precision' => $this->_precision, 'scale' => $this->_scale, 'fixed' => $this->_fixed, 'unsigned' => $this->_unsigned, 'autoincrement' => $this->_autoincrement, 'columnDefinition' => $this->_columnDefinition, 'comment' => $this->_comment, ], $this->_platformOptions, $this->_customSchemaOptions); } } dbal/src/Schema/Index.php 0000644 00000021345 15021222234 0011213 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use InvalidArgumentException; use function array_filter; use function array_keys; use function array_map; use function array_search; use function array_shift; use function count; use function strtolower; class Index extends AbstractAsset implements Constraint { /** * Asset identifier instances of the column names the index is associated with. * array($columnName => Identifier) * * @var Identifier[] */ protected $_columns = []; /** @var bool */ protected $_isUnique = false; /** @var bool */ protected $_isPrimary = false; /** * Platform specific flags for indexes. * array($flagName => true) * * @var true[] */ protected $_flags = []; /** * Platform specific options * * @todo $_flags should eventually be refactored into options * @var mixed[] */ private array $options = []; /** * @param string $name * @param string[] $columns * @param bool $isUnique * @param bool $isPrimary * @param string[] $flags * @param mixed[] $options */ public function __construct( $name, array $columns, $isUnique = false, $isPrimary = false, array $flags = [], array $options = [] ) { $isUnique = $isUnique || $isPrimary; $this->_setName($name); $this->_isUnique = $isUnique; $this->_isPrimary = $isPrimary; $this->options = $options; foreach ($columns as $column) { $this->_addColumn($column); } foreach ($flags as $flag) { $this->addFlag($flag); } } /** @throws InvalidArgumentException */ protected function _addColumn(string $column): void { $this->_columns[$column] = new Identifier($column); } /** * {@inheritDoc} */ public function getColumns() { return array_keys($this->_columns); } /** * {@inheritDoc} */ public function getQuotedColumns(AbstractPlatform $platform) { $subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths') ? $this->getOption('lengths') : []; $columns = []; foreach ($this->_columns as $column) { $length = array_shift($subParts); $quotedColumn = $column->getQuotedName($platform); if ($length !== null) { $quotedColumn .= '(' . $length . ')'; } $columns[] = $quotedColumn; } return $columns; } /** @return string[] */ public function getUnquotedColumns() { return array_map([$this, 'trimQuotes'], $this->getColumns()); } /** * Is the index neither unique nor primary key? * * @return bool */ public function isSimpleIndex() { return ! $this->_isPrimary && ! $this->_isUnique; } /** @return bool */ public function isUnique() { return $this->_isUnique; } /** @return bool */ public function isPrimary() { return $this->_isPrimary; } /** * @param string $name * @param int $pos * * @return bool */ public function hasColumnAtPosition($name, $pos = 0) { $name = $this->trimQuotes(strtolower($name)); $indexColumns = array_map('strtolower', $this->getUnquotedColumns()); return array_search($name, $indexColumns, true) === $pos; } /** * Checks if this index exactly spans the given column names in the correct order. * * @param string[] $columnNames * * @return bool */ public function spansColumns(array $columnNames) { $columns = $this->getColumns(); $numberOfColumns = count($columns); $sameColumns = true; for ($i = 0; $i < $numberOfColumns; $i++) { if ( isset($columnNames[$i]) && $this->trimQuotes(strtolower($columns[$i])) === $this->trimQuotes(strtolower($columnNames[$i])) ) { continue; } $sameColumns = false; } return $sameColumns; } /** * Keeping misspelled function name for backwards compatibility * * @deprecated Use {@see isFulfilledBy()} instead. * * @return bool */ public function isFullfilledBy(Index $other) { return $this->isFulfilledBy($other); } /** * Checks if the other index already fulfills all the indexing and constraint needs of the current one. */ public function isFulfilledBy(Index $other): bool { // allow the other index to be equally large only. It being larger is an option // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo) if (count($other->getColumns()) !== count($this->getColumns())) { return false; } // Check if columns are the same, and even in the same order $sameColumns = $this->spansColumns($other->getColumns()); if ($sameColumns) { if (! $this->samePartialIndex($other)) { return false; } if (! $this->hasSameColumnLengths($other)) { return false; } if (! $this->isUnique() && ! $this->isPrimary()) { // this is a special case: If the current key is neither primary or unique, any unique or // primary key will always have the same effect for the index and there cannot be any constraint // overlaps. This means a primary or unique index can always fulfill the requirements of just an // index that has no constraints. return true; } if ($other->isPrimary() !== $this->isPrimary()) { return false; } return $other->isUnique() === $this->isUnique(); } return false; } /** * Detects if the other index is a non-unique, non primary index that can be overwritten by this one. * * @return bool */ public function overrules(Index $other) { if ($other->isPrimary()) { return false; } if ($this->isSimpleIndex() && $other->isUnique()) { return false; } return $this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique()) && $this->samePartialIndex($other); } /** * Returns platform specific flags for indexes. * * @return string[] */ public function getFlags() { return array_keys($this->_flags); } /** * Adds Flag for an index that translates to platform specific handling. * * @param string $flag * * @return Index * * @example $index->addFlag('CLUSTERED') */ public function addFlag($flag) { $this->_flags[strtolower($flag)] = true; return $this; } /** * Does this index have a specific flag? * * @param string $flag * * @return bool */ public function hasFlag($flag) { return isset($this->_flags[strtolower($flag)]); } /** * Removes a flag. * * @param string $flag * * @return void */ public function removeFlag($flag) { unset($this->_flags[strtolower($flag)]); } /** * @param string $name * * @return bool */ public function hasOption($name) { return isset($this->options[strtolower($name)]); } /** * @param string $name * * @return mixed */ public function getOption($name) { return $this->options[strtolower($name)]; } /** @return mixed[] */ public function getOptions() { return $this->options; } /** * Return whether the two indexes have the same partial index */ private function samePartialIndex(Index $other): bool { if ( $this->hasOption('where') && $other->hasOption('where') && $this->getOption('where') === $other->getOption('where') ) { return true; } return ! $this->hasOption('where') && ! $other->hasOption('where'); } /** * Returns whether the index has the same column lengths as the other */ private function hasSameColumnLengths(self $other): bool { $filter = static function (?int $length): bool { return $length !== null; }; return array_filter($this->options['lengths'] ?? [], $filter) === array_filter($other->options['lengths'] ?? [], $filter); } } dbal/src/Schema/UniqueConstraint.php 0000644 00000006307 15021222234 0013460 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use function array_keys; use function array_map; use function strtolower; /** * Class for a unique constraint. */ class UniqueConstraint extends AbstractAsset implements Constraint { /** * Asset identifier instances of the column names the unique constraint is associated with. * array($columnName => Identifier) * * @var Identifier[] */ protected $columns = []; /** * Platform specific flags. * array($flagName => true) * * @var true[] */ protected $flags = []; /** * Platform specific options. * * @var mixed[] */ private array $options; /** * @param string[] $columns * @param string[] $flags * @param mixed[] $options */ public function __construct(string $name, array $columns, array $flags = [], array $options = []) { $this->_setName($name); $this->options = $options; foreach ($columns as $column) { $this->addColumn($column); } foreach ($flags as $flag) { $this->addFlag($flag); } } /** * {@inheritDoc} */ public function getColumns() { return array_keys($this->columns); } /** * {@inheritDoc} */ public function getQuotedColumns(AbstractPlatform $platform) { $columns = []; foreach ($this->columns as $column) { $columns[] = $column->getQuotedName($platform); } return $columns; } /** @return string[] */ public function getUnquotedColumns(): array { return array_map([$this, 'trimQuotes'], $this->getColumns()); } /** * Returns platform specific flags for unique constraint. * * @return string[] */ public function getFlags(): array { return array_keys($this->flags); } /** * Adds flag for a unique constraint that translates to platform specific handling. * * @return $this * * @example $uniqueConstraint->addFlag('CLUSTERED') */ public function addFlag(string $flag): UniqueConstraint { $this->flags[strtolower($flag)] = true; return $this; } /** * Does this unique constraint have a specific flag? */ public function hasFlag(string $flag): bool { return isset($this->flags[strtolower($flag)]); } /** * Removes a flag. */ public function removeFlag(string $flag): void { unset($this->flags[strtolower($flag)]); } /** * Does this unique constraint have a specific option? */ public function hasOption(string $name): bool { return isset($this->options[strtolower($name)]); } /** @return mixed */ public function getOption(string $name) { return $this->options[strtolower($name)]; } /** @return mixed[] */ public function getOptions(): array { return $this->options; } /** * Adds a new column to the unique constraint. */ protected function addColumn(string $column): void { $this->columns[$column] = new Identifier($column); } } dbal/src/Schema/MySQLSchemaManager.php 0000644 00000046040 15021222234 0013524 0 ustar 00 <?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; } } dbal/src/Schema/SQLServerSchemaManager.php 0000644 00000045466 15021222234 0014420 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\SQLServer; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_change_key_case; use function assert; use function count; use function explode; use function implode; use function is_string; use function preg_match; use function sprintf; use function str_replace; use function strpos; use function strtok; use const CASE_LOWER; /** * SQL Server Schema Manager. * * @extends AbstractSchemaManager<SQLServerPlatform> */ class SQLServerSchemaManager extends AbstractSchemaManager { private ?string $databaseCollation = 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); } /** * {@inheritDoc} */ public function listSchemaNames(): array { return $this->_conn->fetchFirstColumn( <<<'SQL' SELECT name FROM sys.schemas WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys') SQL, ); } /** * {@inheritDoc} */ protected function _getPortableSequenceDefinition($sequence) { return new Sequence($sequence['name'], (int) $sequence['increment'], (int) $sequence['start_value']); } /** * {@inheritDoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $dbType = strtok($tableColumn['type'], '(), '); assert(is_string($dbType)); $fixed = null; $length = (int) $tableColumn['length']; $default = $tableColumn['default']; if (! isset($tableColumn['name'])) { $tableColumn['name'] = ''; } if ($default !== null) { $default = $this->parseDefaultExpression($default); } switch ($dbType) { case 'nchar': case 'ntext': // Unicode data requires 2 bytes per character $length /= 2; break; case 'nvarchar': if ($length === -1) { break; } // Unicode data requires 2 bytes per character $length /= 2; break; case 'varchar': // TEXT type is returned as VARCHAR(MAX) with a length of -1 if ($length === -1) { $dbType = 'text'; } break; case 'varbinary': if ($length === -1) { $dbType = 'blob'; } break; } if ($dbType === 'char' || $dbType === 'nchar' || $dbType === 'binary') { $fixed = true; } $type = $this->_platform->getDoctrineTypeMapping($dbType); $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); $options = [ 'unsigned' => false, 'fixed' => (bool) $fixed, 'default' => $default, 'notnull' => (bool) $tableColumn['notnull'], 'scale' => $tableColumn['scale'], 'precision' => $tableColumn['precision'], 'autoincrement' => (bool) $tableColumn['autoincrement'], 'comment' => $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, ]; if ($length !== 0 && ($type === 'text' || $type === 'string' || $type === 'binary')) { $options['length'] = $length; } $column = new Column($tableColumn['name'], Type::getType($type), $options); if (isset($tableColumn['collation']) && $tableColumn['collation'] !== 'NULL') { $column->setPlatformOption('collation', $tableColumn['collation']); } return $column; } private function parseDefaultExpression(string $value): ?string { while (preg_match('/^\((.*)\)$/s', $value, $matches)) { $value = $matches[1]; } if ($value === 'NULL') { return null; } if (preg_match('/^\'(.*)\'$/s', $value, $matches) === 1) { $value = str_replace("''", "'", $matches[1]); } if ($value === 'getdate()') { return $this->_platform->getCurrentTimestampSQL(); } return $value; } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $foreignKeys = []; foreach ($tableForeignKeys as $tableForeignKey) { $name = $tableForeignKey['ForeignKey']; if (! isset($foreignKeys[$name])) { $foreignKeys[$name] = [ 'local_columns' => [$tableForeignKey['ColumnName']], 'foreign_table' => $tableForeignKey['ReferenceTableName'], 'foreign_columns' => [$tableForeignKey['ReferenceColumnName']], 'name' => $name, 'options' => [ 'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']), 'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc']), ], ]; } else { $foreignKeys[$name]['local_columns'][] = $tableForeignKey['ColumnName']; $foreignKeys[$name]['foreign_columns'][] = $tableForeignKey['ReferenceColumnName']; } } return parent::_getPortableTableForeignKeysList($foreignKeys); } /** * {@inheritDoc} */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { foreach ($tableIndexes as &$tableIndex) { $tableIndex['non_unique'] = (bool) $tableIndex['non_unique']; $tableIndex['primary'] = (bool) $tableIndex['primary']; $tableIndex['flags'] = $tableIndex['flags'] ? [$tableIndex['flags']] : null; } return parent::_getPortableTableIndexesList($tableIndexes, $tableName); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { return new ForeignKeyConstraint( $tableForeignKey['local_columns'], $tableForeignKey['foreign_table'], $tableForeignKey['foreign_columns'], $tableForeignKey['name'], $tableForeignKey['options'], ); } /** * {@inheritDoc} */ protected function _getPortableTableDefinition($table) { if ($table['schema_name'] !== 'dbo') { return $table['schema_name'] . '.' . $table['table_name']; } return $table['table_name']; } /** * {@inheritDoc} */ protected function _getPortableDatabaseDefinition($database) { return $database['name']; } /** * {@inheritDoc} * * @deprecated Use {@see listSchemaNames()} instead. */ protected function getPortableNamespaceDefinition(array $namespace) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'SQLServerSchemaManager::getPortableNamespaceDefinition() is deprecated,' . ' use SQLServerSchemaManager::listSchemaNames() instead.', ); return $namespace['name']; } /** * {@inheritDoc} */ protected function _getPortableViewDefinition($view) { // @todo return new View($view['name'], $view['definition']); } /** * {@inheritDoc} */ public function alterTable(TableDiff $tableDiff) { $droppedColumns = $tableDiff->getDroppedColumns(); if (count($droppedColumns) > 0) { $tableName = ($tableDiff->getOldTable() ?? $tableDiff->getName($this->_platform))->getName(); foreach ($droppedColumns as $col) { foreach ($this->getColumnConstraints($tableName, $col->getName()) as $constraint) { $this->_conn->executeStatement( sprintf( 'ALTER TABLE %s DROP CONSTRAINT %s', $tableName, $constraint, ), ); } } } parent::alterTable($tableDiff); } /** * Returns the names of the constraints for a given column. * * @return iterable<string> * * @throws Exception */ private function getColumnConstraints(string $table, string $column): iterable { return $this->_conn->iterateColumn( <<<'SQL' SELECT o.name FROM sys.objects o INNER JOIN sys.objects t ON t.object_id = o.parent_object_id AND t.type = 'U' INNER JOIN sys.default_constraints dc ON dc.object_id = o.object_id INNER JOIN sys.columns c ON c.column_id = dc.parent_column_id AND c.object_id = t.object_id WHERE t.name = ? AND c.name = ? SQL , [$table, $column], ); } /** @throws Exception */ public function createComparator(): Comparator { return new SQLServer\Comparator($this->_platform, $this->getDatabaseCollation()); } /** @throws Exception */ private function getDatabaseCollation(): string { if ($this->databaseCollation === null) { $databaseCollation = $this->_conn->fetchOne( 'SELECT collation_name FROM sys.databases WHERE name = ' . $this->_platform->getCurrentDatabaseExpression(), ); // a database is always selected, even if omitted in the connection parameters assert(is_string($databaseCollation)); $this->databaseCollation = $databaseCollation; } return $this->databaseCollation; } protected function selectTableNames(string $databaseName): Result { // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams $sql = <<<'SQL' SELECT name AS table_name, SCHEMA_NAME(schema_id) AS schema_name FROM sys.objects WHERE type = 'U' AND name != 'sysdiagrams' ORDER BY name SQL; return $this->_conn->executeQuery($sql); } protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' obj.name AS table_name, scm.name AS schema_name,'; } $sql .= <<<'SQL' col.name, type.name AS type, col.max_length AS length, ~col.is_nullable AS notnull, def.definition AS [default], col.scale, col.precision, col.is_identity AS autoincrement, col.collation_name AS collation, -- CAST avoids driver error for sql_variant type CAST(prop.value AS NVARCHAR(MAX)) AS comment FROM sys.columns AS col JOIN sys.types AS type ON col.user_type_id = type.user_type_id JOIN sys.objects AS obj ON col.object_id = obj.object_id JOIN sys.schemas AS scm ON obj.schema_id = scm.schema_id LEFT JOIN sys.default_constraints def ON col.default_object_id = def.object_id AND col.object_id = def.parent_object_id LEFT JOIN sys.extended_properties AS prop ON obj.object_id = prop.major_id AND col.column_id = prop.minor_id AND prop.name = 'MS_Description' SQL; // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams $conditions = ["obj.type = 'U'", "obj.name != 'sysdiagrams'"]; $params = []; if ($tableName !== null) { $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'obj.name'); } $sql .= ' WHERE ' . implode(' AND ', $conditions); return $this->_conn->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' tbl.name AS table_name, scm.name AS schema_name,'; } $sql .= <<<'SQL' idx.name AS key_name, col.name AS column_name, ~idx.is_unique AS non_unique, idx.is_primary_key AS [primary], CASE idx.type WHEN '1' THEN 'clustered' WHEN '2' THEN 'nonclustered' ELSE NULL END AS flags FROM sys.tables AS tbl JOIN sys.schemas AS scm ON tbl.schema_id = scm.schema_id JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id SQL; $conditions = []; $params = []; if ($tableName !== null) { $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'tbl.name'); $sql .= ' WHERE ' . implode(' AND ', $conditions); } $sql .= ' ORDER BY idx.index_id, idxcol.key_ordinal'; return $this->_conn->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' OBJECT_NAME (f.parent_object_id) AS table_name, SCHEMA_NAME(f.schema_id) AS schema_name,'; } $sql .= <<<'SQL' f.name AS ForeignKey, SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, OBJECT_NAME (f.parent_object_id) AS TableName, COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, f.delete_referential_action_desc, f.update_referential_action_desc FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id ON f.OBJECT_ID = fc.constraint_object_id SQL; $conditions = []; $params = []; if ($tableName !== null) { $conditions[] = $this->getTableWhereClause( $tableName, 'SCHEMA_NAME(f.schema_id)', 'OBJECT_NAME(f.parent_object_id)', ); $sql .= ' WHERE ' . implode(' AND ', $conditions); } $sql .= ' ORDER BY fc.constraint_column_id'; return $this->_conn->executeQuery($sql, $params); } /** * {@inheritDoc} */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { $sql = <<<'SQL' SELECT tbl.name, p.value AS [table_comment] FROM sys.tables AS tbl INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1 SQL; $conditions = ["SCHEMA_NAME(tbl.schema_id) = N'dbo'", "p.name = N'MS_Description'"]; $params = []; if ($tableName !== null) { $conditions[] = "tbl.name = N'" . $tableName . "'"; } $sql .= ' 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['table_comment'], ]; } return $tableOptions; } /** * Returns the where clause to filter schema and table name in a query. * * @param string $table The full qualified name of the table. * @param string $schemaColumn The name of the column to compare the schema to in the where clause. * @param string $tableColumn The name of the column to compare the table to in the where clause. */ private function getTableWhereClause($table, $schemaColumn, $tableColumn): string { if (strpos($table, '.') !== false) { [$schema, $table] = explode('.', $table); $schema = $this->_platform->quoteStringLiteral($schema); $table = $this->_platform->quoteStringLiteral($table); } else { $schema = 'SCHEMA_NAME()'; $table = $this->_platform->quoteStringLiteral($table); } return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); } } dbal/src/Schema/Schema.php 0000644 00000031247 15021222234 0011346 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\DBAL\SQL\Builder\CreateSchemaObjectsSQLBuilder; use Doctrine\DBAL\SQL\Builder\DropSchemaObjectsSQLBuilder; use Doctrine\Deprecations\Deprecation; use function array_keys; use function strpos; use function strtolower; /** * Object representation of a database schema. * * Different vendors have very inconsistent naming with regard to the concept * of a "schema". Doctrine understands a schema as the entity that conceptually * wraps a set of database objects such as tables, sequences, indexes and * foreign keys that belong to each other into a namespace. A Doctrine Schema * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL. * * Every asset in the doctrine schema has a name. A name consists of either a * namespace.local name pair or just a local unqualified name. * * The abstraction layer that covers a PostgreSQL schema is the namespace of an * database object (asset). A schema can have a name, which will be used as * default namespace for the unqualified database objects that are created in * the schema. * * In the case of MySQL where cross-database queries are allowed this leads to * databases being "misinterpreted" as namespaces. This is intentional, however * the CREATE/DROP SQL visitors will just filter this queries and do not * execute them. Only the queries for the currently connected database are * executed. */ class Schema extends AbstractAsset { /** * The namespaces in this schema. * * @var string[] */ private array $namespaces = []; /** @var Table[] */ protected $_tables = []; /** @var Sequence[] */ protected $_sequences = []; /** @var SchemaConfig */ protected $_schemaConfig; /** * @param Table[] $tables * @param Sequence[] $sequences * @param string[] $namespaces * * @throws SchemaException */ public function __construct( array $tables = [], array $sequences = [], ?SchemaConfig $schemaConfig = null, array $namespaces = [] ) { $schemaConfig ??= new SchemaConfig(); $this->_schemaConfig = $schemaConfig; $this->_setName($schemaConfig->getName() ?? 'public'); foreach ($namespaces as $namespace) { $this->createNamespace($namespace); } foreach ($tables as $table) { $this->_addTable($table); } foreach ($sequences as $sequence) { $this->_addSequence($sequence); } } /** * @deprecated * * @return bool */ public function hasExplicitForeignKeyIndexes() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4822', 'Schema::hasExplicitForeignKeyIndexes() is deprecated.', ); return $this->_schemaConfig->hasExplicitForeignKeyIndexes(); } /** * @return void * * @throws SchemaException */ protected function _addTable(Table $table) { $namespaceName = $table->getNamespaceName(); $tableName = $this->normalizeName($table); if (isset($this->_tables[$tableName])) { throw SchemaException::tableAlreadyExists($tableName); } if ( $namespaceName !== null && ! $table->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName) ) { $this->createNamespace($namespaceName); } $this->_tables[$tableName] = $table; $table->setSchemaConfig($this->_schemaConfig); } /** * @return void * * @throws SchemaException */ protected function _addSequence(Sequence $sequence) { $namespaceName = $sequence->getNamespaceName(); $seqName = $this->normalizeName($sequence); if (isset($this->_sequences[$seqName])) { throw SchemaException::sequenceAlreadyExists($seqName); } if ( $namespaceName !== null && ! $sequence->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName) ) { $this->createNamespace($namespaceName); } $this->_sequences[$seqName] = $sequence; } /** * Returns the namespaces of this schema. * * @return string[] A list of namespace names. */ public function getNamespaces() { return $this->namespaces; } /** * Gets all tables of this schema. * * @return Table[] */ public function getTables() { return $this->_tables; } /** * @param string $name * * @return Table * * @throws SchemaException */ public function getTable($name) { $name = $this->getFullQualifiedAssetName($name); if (! isset($this->_tables[$name])) { throw SchemaException::tableDoesNotExist($name); } return $this->_tables[$name]; } /** @param string $name */ private function getFullQualifiedAssetName($name): string { $name = $this->getUnquotedAssetName($name); if (strpos($name, '.') === false) { $name = $this->getName() . '.' . $name; } return strtolower($name); } private function normalizeName(AbstractAsset $asset): string { return $asset->getFullQualifiedName($this->getName()); } /** * Returns the unquoted representation of a given asset name. * * @param string $assetName Quoted or unquoted representation of an asset name. */ private function getUnquotedAssetName($assetName): string { if ($this->isIdentifierQuoted($assetName)) { return $this->trimQuotes($assetName); } return $assetName; } /** * Does this schema have a namespace with the given name? * * @param string $name * * @return bool */ public function hasNamespace($name) { $name = strtolower($this->getUnquotedAssetName($name)); return isset($this->namespaces[$name]); } /** * Does this schema have a table with the given name? * * @param string $name * * @return bool */ public function hasTable($name) { $name = $this->getFullQualifiedAssetName($name); return isset($this->_tables[$name]); } /** * Gets all table names, prefixed with a schema name, even the default one if present. * * @deprecated Use {@see getTables()} and {@see Table::getName()} instead. * * @return string[] */ public function getTableNames() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4800', 'Schema::getTableNames() is deprecated.' . ' Use Schema::getTables() and Table::getName() instead.', __METHOD__, ); return array_keys($this->_tables); } /** * @param string $name * * @return bool */ public function hasSequence($name) { $name = $this->getFullQualifiedAssetName($name); return isset($this->_sequences[$name]); } /** * @param string $name * * @return Sequence * * @throws SchemaException */ public function getSequence($name) { $name = $this->getFullQualifiedAssetName($name); if (! $this->hasSequence($name)) { throw SchemaException::sequenceDoesNotExist($name); } return $this->_sequences[$name]; } /** @return Sequence[] */ public function getSequences() { return $this->_sequences; } /** * Creates a new namespace. * * @param string $name The name of the namespace to create. * * @return Schema This schema instance. * * @throws SchemaException */ public function createNamespace($name) { $unquotedName = strtolower($this->getUnquotedAssetName($name)); if (isset($this->namespaces[$unquotedName])) { throw SchemaException::namespaceAlreadyExists($unquotedName); } $this->namespaces[$unquotedName] = $name; return $this; } /** * Creates a new table. * * @param string $name * * @return Table * * @throws SchemaException */ public function createTable($name) { $table = new Table($name); $this->_addTable($table); foreach ($this->_schemaConfig->getDefaultTableOptions() as $option => $value) { $table->addOption($option, $value); } return $table; } /** * Renames a table. * * @param string $oldName * @param string $newName * * @return Schema * * @throws SchemaException */ public function renameTable($oldName, $newName) { $table = $this->getTable($oldName); $table->_setName($newName); $this->dropTable($oldName); $this->_addTable($table); return $this; } /** * Drops a table from the schema. * * @param string $name * * @return Schema * * @throws SchemaException */ public function dropTable($name) { $name = $this->getFullQualifiedAssetName($name); $this->getTable($name); unset($this->_tables[$name]); return $this; } /** * Creates a new sequence. * * @param string $name * @param int $allocationSize * @param int $initialValue * * @return Sequence * * @throws SchemaException */ public function createSequence($name, $allocationSize = 1, $initialValue = 1) { $seq = new Sequence($name, $allocationSize, $initialValue); $this->_addSequence($seq); return $seq; } /** * @param string $name * * @return Schema */ public function dropSequence($name) { $name = $this->getFullQualifiedAssetName($name); unset($this->_sequences[$name]); return $this; } /** * Returns an array of necessary SQL queries to create the schema on the given platform. * * @return list<string> * * @throws Exception */ public function toSql(AbstractPlatform $platform) { $builder = new CreateSchemaObjectsSQLBuilder($platform); return $builder->buildSQL($this); } /** * Return an array of necessary SQL queries to drop the schema on the given platform. * * @return list<string> * * @throws Exception */ public function toDropSql(AbstractPlatform $platform) { $builder = new DropSchemaObjectsSQLBuilder($platform); return $builder->buildSQL($this); } /** * @deprecated * * @return string[] * * @throws SchemaException */ public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform) { $schemaDiff = (new Comparator())->compareSchemas($this, $toSchema); return $schemaDiff->toSql($platform); } /** * @deprecated * * @return string[] * * @throws SchemaException */ public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform) { $schemaDiff = (new Comparator())->compareSchemas($fromSchema, $this); return $schemaDiff->toSql($platform); } /** * @deprecated * * @return void */ public function visit(Visitor $visitor) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5435', 'Schema::visit() is deprecated.', ); $visitor->acceptSchema($this); if ($visitor instanceof NamespaceVisitor) { foreach ($this->namespaces as $namespace) { $visitor->acceptNamespace($namespace); } } foreach ($this->_tables as $table) { $table->visit($visitor); } foreach ($this->_sequences as $sequence) { $sequence->visit($visitor); } } /** * Cloning a Schema triggers a deep clone of all related assets. * * @return void */ public function __clone() { foreach ($this->_tables as $k => $table) { $this->_tables[$k] = clone $table; } foreach ($this->_sequences as $k => $sequence) { $this->_sequences[$k] = clone $sequence; } } } dbal/src/Schema/Identifier.php 0000644 00000001232 15021222234 0012217 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; /** * An abstraction class for an asset identifier. * * Wraps identifier names like column names in indexes / foreign keys * in an abstract class for proper quotation capabilities. */ class Identifier extends AbstractAsset { /** * @param string $identifier Identifier name to wrap. * @param bool $quote Whether to force quoting the given identifier. */ public function __construct($identifier, $quote = false) { $this->_setName($identifier); if (! $quote || $this->_quoted) { return; } $this->_setName('"' . $this->getName() . '"'); } } dbal/src/Schema/AbstractSchemaManager.php 0000644 00000144225 15021222234 0014326 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs; use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\DatabaseRequired; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Result; use Doctrine\Deprecations\Deprecation; use Throwable; use function array_filter; use function array_intersect; use function array_map; use function array_values; use function assert; use function call_user_func_array; use function count; use function func_get_args; use function is_callable; use function is_string; use function preg_match; use function str_replace; use function strtolower; /** * Base class for schema managers. Schema managers are used to inspect and/or * modify the database schema/structure. * * @template-covariant T of AbstractPlatform */ abstract class AbstractSchemaManager { /** * Holds instance of the Doctrine connection for this schema manager. * * @var Connection */ protected $_conn; /** * Holds instance of the database platform used for this schema manager. * * @var T */ protected $_platform; /** @param T $platform */ public function __construct(Connection $connection, AbstractPlatform $platform) { $this->_conn = $connection; $this->_platform = $platform; } /** * Returns the associated platform. * * @deprecated Use {@link Connection::getDatabasePlatform()} instead. * * @return T */ public function getDatabasePlatform() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5387', 'AbstractSchemaManager::getDatabasePlatform() is deprecated.' . ' Use Connection::getDatabasePlatform() instead.', ); return $this->_platform; } /** * Tries any method on the schema manager. Normally a method throws an * exception when your DBMS doesn't support it or if an error occurs. * This method allows you to try and method on your SchemaManager * instance and will return false if it does not work or is not supported. * * <code> * $result = $sm->tryMethod('dropView', 'view_name'); * </code> * * @deprecated * * @return mixed */ public function tryMethod() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::tryMethod() is deprecated.', ); $args = func_get_args(); $method = $args[0]; unset($args[0]); $args = array_values($args); $callback = [$this, $method]; assert(is_callable($callback)); try { return call_user_func_array($callback, $args); } catch (Throwable $e) { return false; } } /** * Lists the available databases for this connection. * * @return string[] * * @throws Exception */ public function listDatabases() { $sql = $this->_platform->getListDatabasesSQL(); $databases = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableDatabasesList($databases); } /** * Returns a list of all namespaces in the current database. * * @deprecated Use {@see listSchemaNames()} instead. * * @return string[] * * @throws Exception */ public function listNamespaceNames() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'AbstractSchemaManager::listNamespaceNames() is deprecated,' . ' use AbstractSchemaManager::listSchemaNames() instead.', ); $sql = $this->_platform->getListNamespacesSQL(); $namespaces = $this->_conn->fetchAllAssociative($sql); return $this->getPortableNamespacesList($namespaces); } /** * Returns a list of the names of all schemata in the current database. * * @return list<string> * * @throws Exception */ public function listSchemaNames(): array { throw Exception::notSupported(__METHOD__); } /** * Lists the available sequences for this connection. * * @param string|null $database * * @return Sequence[] * * @throws Exception */ public function listSequences($database = null) { if ($database === null) { $database = $this->getDatabase(__METHOD__); } else { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5284', 'Passing $database to AbstractSchemaManager::listSequences() is deprecated.', ); } $sql = $this->_platform->getListSequencesSQL($database); $sequences = $this->_conn->fetchAllAssociative($sql); return $this->filterAssetNames($this->_getPortableSequencesList($sequences)); } /** * Lists the columns for a given table. * * In contrast to other libraries and to the old version of Doctrine, * this column definition does try to contain the 'primary' column for * the reason that it is not portable across different RDBMS. Use * {@see listTableIndexes($tableName)} to retrieve the primary key * of a table. Where a RDBMS specifies more details, these are held * in the platformDetails array. * * @param string $table The name of the table. * @param string|null $database * * @return Column[] * * @throws Exception */ public function listTableColumns($table, $database = null) { if ($database === null) { $database = $this->getDatabase(__METHOD__); } else { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5284', 'Passing $database to AbstractSchemaManager::listTableColumns() is deprecated.', ); } $sql = $this->_platform->getListTableColumnsSQL($table, $database); $tableColumns = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableTableColumnList($table, $database, $tableColumns); } /** * @param string $table * @param string|null $database * * @return Column[] * * @throws Exception */ protected function doListTableColumns($table, $database = null): array { if ($database === null) { $database = $this->getDatabase(__METHOD__); } else { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5284', 'Passing $database to AbstractSchemaManager::doListTableColumns() is deprecated.', ); } return $this->_getPortableTableColumnList( $table, $database, $this->selectTableColumns($database, $this->normalizeName($table)) ->fetchAllAssociative(), ); } /** * Lists the indexes for a given table returning an array of Index instances. * * Keys of the portable indexes list are all lower-cased. * * @param string $table The name of the table. * * @return Index[] * * @throws Exception */ public function listTableIndexes($table) { $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase()); $tableIndexes = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableTableIndexesList($tableIndexes, $table); } /** * @param string $table * * @return Index[] * * @throws Exception */ protected function doListTableIndexes($table): array { $database = $this->getDatabase(__METHOD__); $table = $this->normalizeName($table); return $this->_getPortableTableIndexesList( $this->selectIndexColumns( $database, $table, )->fetchAllAssociative(), $table, ); } /** * Returns true if all the given tables exist. * * The usage of a string $tableNames is deprecated. Pass a one-element array instead. * * @param string|string[] $names * * @return bool * * @throws Exception */ public function tablesExist($names) { if (is_string($names)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'The usage of a string $tableNames in AbstractSchemaManager::tablesExist() is deprecated. ' . 'Pass a one-element array instead.', ); } $names = array_map('strtolower', (array) $names); return count($names) === count(array_intersect($names, array_map('strtolower', $this->listTableNames()))); } /** * Returns a list of all tables in the current database. * * @return string[] * * @throws Exception */ public function listTableNames() { $sql = $this->_platform->getListTablesSQL(); $tables = $this->_conn->fetchAllAssociative($sql); $tableNames = $this->_getPortableTablesList($tables); return $this->filterAssetNames($tableNames); } /** * @return list<string> * * @throws Exception */ protected function doListTableNames(): array { $database = $this->getDatabase(__METHOD__); return $this->filterAssetNames( $this->_getPortableTablesList( $this->selectTableNames($database) ->fetchAllAssociative(), ), ); } /** * Filters asset names if they are configured to return only a subset of all * the found elements. * * @param mixed[] $assetNames * * @return mixed[] */ protected function filterAssetNames($assetNames) { $filter = $this->_conn->getConfiguration()->getSchemaAssetsFilter(); if ($filter === null) { return $assetNames; } return array_values(array_filter($assetNames, $filter)); } /** * Lists the tables for this connection. * * @return list<Table> * * @throws Exception */ public function listTables() { $tableNames = $this->listTableNames(); $tables = []; foreach ($tableNames as $tableName) { $tables[] = $this->introspectTable($tableName); } return $tables; } /** * @return list<Table> * * @throws Exception */ protected function doListTables(): array { $database = $this->getDatabase(__METHOD__); $tableColumnsByTable = $this->fetchTableColumnsByTable($database); $indexColumnsByTable = $this->fetchIndexColumnsByTable($database); $foreignKeyColumnsByTable = $this->fetchForeignKeyColumnsByTable($database); $tableOptionsByTable = $this->fetchTableOptionsByTable($database); $filter = $this->_conn->getConfiguration()->getSchemaAssetsFilter(); $tables = []; foreach ($tableColumnsByTable as $tableName => $tableColumns) { if ($filter !== null && ! $filter($tableName)) { continue; } $tables[] = new Table( $tableName, $this->_getPortableTableColumnList($tableName, $database, $tableColumns), $this->_getPortableTableIndexesList($indexColumnsByTable[$tableName] ?? [], $tableName), [], $this->_getPortableTableForeignKeysList($foreignKeyColumnsByTable[$tableName] ?? []), $tableOptionsByTable[$tableName] ?? [], ); } return $tables; } /** * @deprecated Use {@see introspectTable()} instead. * * @param string $name * * @return Table * * @throws Exception */ public function listTableDetails($name) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5595', '%s is deprecated. Use introspectTable() instead.', __METHOD__, ); $columns = $this->listTableColumns($name); $foreignKeys = []; if ($this->_platform->supportsForeignKeyConstraints()) { $foreignKeys = $this->listTableForeignKeys($name); } $indexes = $this->listTableIndexes($name); return new Table($name, $columns, $indexes, [], $foreignKeys); } /** * @param string $name * * @throws Exception */ protected function doListTableDetails($name): Table { $database = $this->getDatabase(__METHOD__); $normalizedName = $this->normalizeName($name); $tableOptionsByTable = $this->fetchTableOptionsByTable($database, $normalizedName); if ($this->_platform->supportsForeignKeyConstraints()) { $foreignKeys = $this->listTableForeignKeys($name); } else { $foreignKeys = []; } return new Table( $name, $this->listTableColumns($name, $database), $this->listTableIndexes($name), [], $foreignKeys, $tableOptionsByTable[$normalizedName] ?? [], ); } /** * An extension point for those platforms where case sensitivity of the object name depends on whether it's quoted. * * Such platforms should convert a possibly quoted name into a value of the corresponding case. */ protected function normalizeName(string $name): string { $identifier = new Identifier($name); return $identifier->getName(); } /** * Selects names of tables in the specified database. * * @throws Exception * * @abstract */ protected function selectTableNames(string $databaseName): Result { throw Exception::notSupported(__METHOD__); } /** * Selects definitions of table columns in the specified database. If the table name is specified, narrows down * the selection to this table. * * @throws Exception * * @abstract */ protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { throw Exception::notSupported(__METHOD__); } /** * Selects definitions of index columns in the specified database. If the table name is specified, narrows down * the selection to this table. * * @throws Exception */ protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { throw Exception::notSupported(__METHOD__); } /** * Selects definitions of foreign key columns in the specified database. If the table name is specified, * narrows down the selection to this table. * * @throws Exception */ protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { throw Exception::notSupported(__METHOD__); } /** * Fetches definitions of table columns in the specified database and returns them grouped by table name. * * @return array<string,list<array<string,mixed>>> * * @throws Exception */ protected function fetchTableColumnsByTable(string $databaseName): array { return $this->fetchAllAssociativeGrouped($this->selectTableColumns($databaseName)); } /** * Fetches definitions of index columns in the specified database and returns them grouped by table name. * * @return array<string,list<array<string,mixed>>> * * @throws Exception */ protected function fetchIndexColumnsByTable(string $databaseName): array { return $this->fetchAllAssociativeGrouped($this->selectIndexColumns($databaseName)); } /** * Fetches definitions of foreign key columns in the specified database and returns them grouped by table name. * * @return array<string, list<array<string, mixed>>> * * @throws Exception */ protected function fetchForeignKeyColumnsByTable(string $databaseName): array { if (! $this->_platform->supportsForeignKeyConstraints()) { return []; } return $this->fetchAllAssociativeGrouped( $this->selectForeignKeyColumns($databaseName), ); } /** * Fetches table options for the tables in the specified database and returns them grouped by table name. * If the table name is specified, narrows down the selection to this table. * * @return array<string,array<string,mixed>> * * @throws Exception */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { throw Exception::notSupported(__METHOD__); } /** * Introspects the table with the given name. * * @throws Exception */ public function introspectTable(string $name): Table { $table = $this->listTableDetails($name); if ($table->getColumns() === []) { throw SchemaException::tableDoesNotExist($name); } return $table; } /** * Lists the views this connection has. * * @return View[] * * @throws Exception */ public function listViews() { $database = $this->_conn->getDatabase(); $sql = $this->_platform->getListViewsSQL($database); $views = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableViewsList($views); } /** * Lists the foreign keys for the given table. * * @param string $table The name of the table. * @param string|null $database * * @return ForeignKeyConstraint[] * * @throws Exception */ public function listTableForeignKeys($table, $database = null) { if ($database === null) { $database = $this->getDatabase(__METHOD__); } else { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5284', 'Passing $database to AbstractSchemaManager::listTableForeignKeys() is deprecated.', ); } $sql = $this->_platform->getListTableForeignKeysSQL($table, $database); $tableForeignKeys = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableTableForeignKeysList($tableForeignKeys); } /** * @param string $table * @param string|null $database * * @return ForeignKeyConstraint[] * * @throws Exception */ protected function doListTableForeignKeys($table, $database = null): array { if ($database === null) { $database = $this->getDatabase(__METHOD__); } else { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5284', 'Passing $database to AbstractSchemaManager::listTableForeignKeys() is deprecated.', ); } return $this->_getPortableTableForeignKeysList( $this->selectForeignKeyColumns( $database, $this->normalizeName($table), )->fetchAllAssociative(), ); } /* drop*() Methods */ /** * Drops a database. * * NOTE: You can not drop the database this SchemaManager is currently connected to. * * @param string $database The name of the database to drop. * * @return void * * @throws Exception */ public function dropDatabase($database) { $this->_conn->executeStatement( $this->_platform->getDropDatabaseSQL($database), ); } /** * Drops a schema. * * @throws Exception */ public function dropSchema(string $schemaName): void { $this->_conn->executeStatement( $this->_platform->getDropSchemaSQL($schemaName), ); } /** * Drops the given table. * * @param string $name The name of the table to drop. * * @return void * * @throws Exception */ public function dropTable($name) { $this->_conn->executeStatement( $this->_platform->getDropTableSQL($name), ); } /** * Drops the index from the given table. * * @param Index|string $index The name of the index. * @param Table|string $table The name of the table. * * @return void * * @throws Exception */ public function dropIndex($index, $table) { if ($index instanceof Index) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $index = $index->getQuotedName($this->_platform); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as an Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this->_platform); } $this->_conn->executeStatement( $this->_platform->getDropIndexSQL($index, $table), ); } /** * Drops the constraint from the given table. * * @deprecated Use {@see dropIndex()}, {@see dropForeignKey()} or {@see dropUniqueConstraint()} instead. * * @param Table|string $table The name of the table. * * @return void * * @throws Exception */ public function dropConstraint(Constraint $constraint, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this->_platform); } $this->_conn->executeStatement($this->_platform->getDropConstraintSQL( $constraint->getQuotedName($this->_platform), $table, )); } /** * Drops a foreign key from a table. * * @param ForeignKeyConstraint|string $foreignKey The name of the foreign key. * @param Table|string $table The name of the table with the foreign key. * * @return void * * @throws Exception */ public function dropForeignKey($foreignKey, $table) { if ($foreignKey instanceof ForeignKeyConstraint) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $foreignKey as a ForeignKeyConstraint object to %s is deprecated.' . ' Pass it as a quoted name instead.', __METHOD__, ); $foreignKey = $foreignKey->getQuotedName($this->_platform); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this->_platform); } $this->_conn->executeStatement( $this->_platform->getDropForeignKeySQL($foreignKey, $table), ); } /** * Drops a sequence with a given name. * * @param string $name The name of the sequence to drop. * * @return void * * @throws Exception */ public function dropSequence($name) { $this->_conn->executeStatement( $this->_platform->getDropSequenceSQL($name), ); } /** * Drops the unique constraint from the given table. * * @throws Exception */ public function dropUniqueConstraint(string $name, string $tableName): void { $this->_conn->executeStatement( $this->_platform->getDropUniqueConstraintSQL($name, $tableName), ); } /** * Drops a view. * * @param string $name The name of the view. * * @return void * * @throws Exception */ public function dropView($name) { $this->_conn->executeStatement( $this->_platform->getDropViewSQL($name), ); } /* create*() Methods */ /** @throws Exception */ public function createSchemaObjects(Schema $schema): void { $this->_execSql($schema->toSql($this->_platform)); } /** * Creates a new database. * * @param string $database The name of the database to create. * * @return void * * @throws Exception */ public function createDatabase($database) { $this->_conn->executeStatement( $this->_platform->getCreateDatabaseSQL($database), ); } /** * Creates a new table. * * @return void * * @throws Exception */ public function createTable(Table $table) { $createFlags = AbstractPlatform::CREATE_INDEXES | AbstractPlatform::CREATE_FOREIGNKEYS; $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags)); } /** * Creates a new sequence. * * @param Sequence $sequence * * @return void * * @throws Exception */ public function createSequence($sequence) { $this->_conn->executeStatement( $this->_platform->getCreateSequenceSQL($sequence), ); } /** * Creates a constraint on a table. * * @deprecated Use {@see createIndex()}, {@see createForeignKey()} or {@see createUniqueConstraint()} instead. * * @param Table|string $table * * @return void * * @throws Exception */ public function createConstraint(Constraint $constraint, $table) { $this->_conn->executeStatement( $this->_platform->getCreateConstraintSQL($constraint, $table), ); } /** * Creates a new index on a table. * * @param Table|string $table The name of the table on which the index is to be created. * * @return void * * @throws Exception */ public function createIndex(Index $index, $table) { $this->_conn->executeStatement( $this->_platform->getCreateIndexSQL($index, $table), ); } /** * Creates a new foreign key. * * @param ForeignKeyConstraint $foreignKey The ForeignKey instance. * @param Table|string $table The name of the table on which the foreign key is to be created. * * @return void * * @throws Exception */ public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) { $this->_conn->executeStatement( $this->_platform->getCreateForeignKeySQL($foreignKey, $table), ); } /** * Creates a unique constraint on a table. * * @throws Exception */ public function createUniqueConstraint(UniqueConstraint $uniqueConstraint, string $tableName): void { $this->_conn->executeStatement( $this->_platform->getCreateUniqueConstraintSQL($uniqueConstraint, $tableName), ); } /** * Creates a new view. * * @return void * * @throws Exception */ public function createView(View $view) { $this->_conn->executeStatement( $this->_platform->getCreateViewSQL( $view->getQuotedName($this->_platform), $view->getSql(), ), ); } /* dropAndCreate*() Methods */ /** @throws Exception */ public function dropSchemaObjects(Schema $schema): void { $this->_execSql($schema->toDropSql($this->_platform)); } /** * Drops and creates a constraint. * * @deprecated Use {@see dropIndex()} and {@see createIndex()}, * {@see dropForeignKey()} and {@see createForeignKey()} * or {@see dropUniqueConstraint()} and {@see createUniqueConstraint()} instead. * * @see dropConstraint() * @see createConstraint() * * @param Table|string $table * * @return void * * @throws Exception */ public function dropAndCreateConstraint(Constraint $constraint, $table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateConstraint() is deprecated.' . ' Use AbstractSchemaManager::dropIndex() and AbstractSchemaManager::createIndex(),' . ' AbstractSchemaManager::dropForeignKey() and AbstractSchemaManager::createForeignKey()' . ' or AbstractSchemaManager::dropUniqueConstraint()' . ' and AbstractSchemaManager::createUniqueConstraint() instead.', ); $this->tryMethod('dropConstraint', $constraint, $table); $this->createConstraint($constraint, $table); } /** * Drops and creates a new index on a table. * * @deprecated Use {@see dropIndex()} and {@see createIndex()} instead. * * @param Table|string $table The name of the table on which the index is to be created. * * @return void * * @throws Exception */ public function dropAndCreateIndex(Index $index, $table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateIndex() is deprecated.' . ' Use AbstractSchemaManager::dropIndex() and AbstractSchemaManager::createIndex() instead.', ); $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table); $this->createIndex($index, $table); } /** * Drops and creates a new foreign key. * * @deprecated Use {@see dropForeignKey()} and {@see createForeignKey()} instead. * * @param ForeignKeyConstraint $foreignKey An associative array that defines properties * of the foreign key to be created. * @param Table|string $table The name of the table on which the foreign key is to be created. * * @return void * * @throws Exception */ public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateForeignKey() is deprecated.' . ' Use AbstractSchemaManager::dropForeignKey() and AbstractSchemaManager::createForeignKey() instead.', ); $this->tryMethod('dropForeignKey', $foreignKey, $table); $this->createForeignKey($foreignKey, $table); } /** * Drops and create a new sequence. * * @deprecated Use {@see dropSequence()} and {@see createSequence()} instead. * * @return void * * @throws Exception */ public function dropAndCreateSequence(Sequence $sequence) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateSequence() is deprecated.' . ' Use AbstractSchemaManager::dropSequence() and AbstractSchemaManager::createSequence() instead.', ); $this->tryMethod('dropSequence', $sequence->getQuotedName($this->_platform)); $this->createSequence($sequence); } /** * Drops and creates a new table. * * @deprecated Use {@see dropTable()} and {@see createTable()} instead. * * @return void * * @throws Exception */ public function dropAndCreateTable(Table $table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateTable() is deprecated.' . ' Use AbstractSchemaManager::dropTable() and AbstractSchemaManager::createTable() instead.', ); $this->tryMethod('dropTable', $table->getQuotedName($this->_platform)); $this->createTable($table); } /** * Drops and creates a new database. * * @deprecated Use {@see dropDatabase()} and {@see createDatabase()} instead. * * @param string $database The name of the database to create. * * @return void * * @throws Exception */ public function dropAndCreateDatabase($database) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateDatabase() is deprecated.' . ' Use AbstractSchemaManager::dropDatabase() and AbstractSchemaManager::createDatabase() instead.', ); $this->tryMethod('dropDatabase', $database); $this->createDatabase($database); } /** * Drops and creates a new view. * * @deprecated Use {@see dropView()} and {@see createView()} instead. * * @return void * * @throws Exception */ public function dropAndCreateView(View $view) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4897', 'AbstractSchemaManager::dropAndCreateView() is deprecated.' . ' Use AbstractSchemaManager::dropView() and AbstractSchemaManager::createView() instead.', ); $this->tryMethod('dropView', $view->getQuotedName($this->_platform)); $this->createView($view); } /** * Alters an existing schema. * * @throws Exception */ public function alterSchema(SchemaDiff $schemaDiff): void { $this->_execSql($this->_platform->getAlterSchemaSQL($schemaDiff)); } /** * Migrates an existing schema to a new schema. * * @throws Exception */ public function migrateSchema(Schema $toSchema): void { $schemaDiff = $this->createComparator() ->compareSchemas($this->introspectSchema(), $toSchema); $this->alterSchema($schemaDiff); } /* alterTable() Methods */ /** * Alters an existing tables schema. * * @return void * * @throws Exception */ public function alterTable(TableDiff $tableDiff) { $this->_execSql($this->_platform->getAlterTableSQL($tableDiff)); } /** * Renames a given table to another name. * * @param string $name The current name of the table. * @param string $newName The new name of the table. * * @return void * * @throws Exception */ public function renameTable($name, $newName) { $this->_execSql($this->_platform->getRenameTableSQL($name, $newName)); } /** * Methods for filtering return values of list*() methods to convert * the native DBMS data definition to a portable Doctrine definition */ /** * @param mixed[] $databases * * @return string[] */ protected function _getPortableDatabasesList($databases) { $list = []; foreach ($databases as $value) { $list[] = $this->_getPortableDatabaseDefinition($value); } return $list; } /** * Converts a list of namespace names from the native DBMS data definition to a portable Doctrine definition. * * @deprecated Use {@see listSchemaNames()} instead. * * @param array<int, array<string, mixed>> $namespaces The list of namespace names * in the native DBMS data definition. * * @return string[] */ protected function getPortableNamespacesList(array $namespaces) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'AbstractSchemaManager::getPortableNamespacesList() is deprecated,' . ' use AbstractSchemaManager::listSchemaNames() instead.', ); $namespacesList = []; foreach ($namespaces as $namespace) { $namespacesList[] = $this->getPortableNamespaceDefinition($namespace); } return $namespacesList; } /** * @param mixed $database * * @return mixed */ protected function _getPortableDatabaseDefinition($database) { return $database; } /** * Converts a namespace definition from the native DBMS data definition to a portable Doctrine definition. * * @deprecated Use {@see listSchemaNames()} instead. * * @param array<string, mixed> $namespace The native DBMS namespace definition. * * @return mixed */ protected function getPortableNamespaceDefinition(array $namespace) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'AbstractSchemaManager::getPortableNamespaceDefinition() is deprecated,' . ' use AbstractSchemaManager::listSchemaNames() instead.', ); return $namespace; } /** * @param mixed[][] $sequences * * @return Sequence[] * * @throws Exception */ protected function _getPortableSequencesList($sequences) { $list = []; foreach ($sequences as $value) { $list[] = $this->_getPortableSequenceDefinition($value); } return $list; } /** * @param mixed[] $sequence * * @return Sequence * * @throws Exception */ protected function _getPortableSequenceDefinition($sequence) { throw Exception::notSupported('Sequences'); } /** * Independent of the database the keys of the column list result are lowercased. * * The name of the created column instance however is kept in its case. * * @param string $table The name of the table. * @param string $database * @param mixed[][] $tableColumns * * @return Column[] * * @throws Exception */ protected function _getPortableTableColumnList($table, $database, $tableColumns) { $eventManager = $this->_platform->getEventManager(); $list = []; foreach ($tableColumns as $tableColumn) { $column = null; $defaultPrevented = false; if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaColumnDefinition)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated. Use a custom schema manager instead.', Events::onSchemaColumnDefinition, ); $eventArgs = new SchemaColumnDefinitionEventArgs($tableColumn, $table, $database, $this->_conn); $eventManager->dispatchEvent(Events::onSchemaColumnDefinition, $eventArgs); $defaultPrevented = $eventArgs->isDefaultPrevented(); $column = $eventArgs->getColumn(); } if (! $defaultPrevented) { $column = $this->_getPortableTableColumnDefinition($tableColumn); } if ($column === null) { continue; } $name = strtolower($column->getQuotedName($this->_platform)); $list[$name] = $column; } return $list; } /** * Gets Table Column Definition. * * @param mixed[] $tableColumn * * @return Column * * @throws Exception */ abstract protected function _getPortableTableColumnDefinition($tableColumn); /** * Aggregates and groups the index results according to the required data result. * * @param mixed[][] $tableIndexes * @param string|null $tableName * * @return Index[] * * @throws Exception */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { $result = []; foreach ($tableIndexes as $tableIndex) { $indexName = $keyName = $tableIndex['key_name']; if ($tableIndex['primary']) { $keyName = 'primary'; } $keyName = strtolower($keyName); if (! isset($result[$keyName])) { $options = [ 'lengths' => [], ]; if (isset($tableIndex['where'])) { $options['where'] = $tableIndex['where']; } $result[$keyName] = [ 'name' => $indexName, 'columns' => [], 'unique' => ! $tableIndex['non_unique'], 'primary' => $tableIndex['primary'], 'flags' => $tableIndex['flags'] ?? [], 'options' => $options, ]; } $result[$keyName]['columns'][] = $tableIndex['column_name']; $result[$keyName]['options']['lengths'][] = $tableIndex['length'] ?? null; } $eventManager = $this->_platform->getEventManager(); $indexes = []; foreach ($result as $indexKey => $data) { $index = null; $defaultPrevented = false; if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated. Use a custom schema manager instead.', Events::onSchemaColumnDefinition, ); $eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn); $eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs); $defaultPrevented = $eventArgs->isDefaultPrevented(); $index = $eventArgs->getIndex(); } if (! $defaultPrevented) { $index = new Index( $data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags'], $data['options'], ); } if ($index === null) { continue; } $indexes[$indexKey] = $index; } return $indexes; } /** * @param mixed[][] $tables * * @return string[] */ protected function _getPortableTablesList($tables) { $list = []; foreach ($tables as $value) { $list[] = $this->_getPortableTableDefinition($value); } return $list; } /** * @param mixed $table * * @return string */ protected function _getPortableTableDefinition($table) { return $table; } /** * @param mixed[][] $views * * @return View[] */ protected function _getPortableViewsList($views) { $list = []; foreach ($views as $value) { $view = $this->_getPortableViewDefinition($value); if ($view === false) { continue; } $viewName = strtolower($view->getQuotedName($this->_platform)); $list[$viewName] = $view; } return $list; } /** * @param mixed[] $view * * @return View|false */ protected function _getPortableViewDefinition($view) { return false; } /** * @param mixed[][] $tableForeignKeys * * @return ForeignKeyConstraint[] */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $list = []; foreach ($tableForeignKeys as $value) { $list[] = $this->_getPortableTableForeignKeyDefinition($value); } return $list; } /** * @param mixed $tableForeignKey * * @return ForeignKeyConstraint * * @abstract */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { return $tableForeignKey; } /** * @internal * * @param string[]|string $sql * * @return void * * @throws Exception */ protected function _execSql($sql) { foreach ((array) $sql as $query) { $this->_conn->executeStatement($query); } } /** * Creates a schema instance for the current database. * * @deprecated Use {@link introspectSchema()} instead. * * @return Schema * * @throws Exception */ public function createSchema() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5613', '%s is deprecated. Use introspectSchema() instead.', __METHOD__, ); $schemaNames = []; if ($this->_platform->supportsSchemas()) { $schemaNames = $this->listNamespaceNames(); } $sequences = []; if ($this->_platform->supportsSequences()) { $sequences = $this->listSequences(); } $tables = $this->listTables(); return new Schema($tables, $sequences, $this->createSchemaConfig(), $schemaNames); } /** * Returns a {@see Schema} instance representing the current database schema. * * @throws Exception */ public function introspectSchema(): Schema { return $this->createSchema(); } /** * Creates the configuration for this schema. * * @return SchemaConfig * * @throws Exception */ public function createSchemaConfig() { $schemaConfig = new SchemaConfig(); $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength()); $searchPaths = $this->getSchemaSearchPaths(); if (isset($searchPaths[0])) { $schemaConfig->setName($searchPaths[0]); } $params = $this->_conn->getParams(); if (! isset($params['defaultTableOptions'])) { $params['defaultTableOptions'] = []; } if (! isset($params['defaultTableOptions']['charset']) && isset($params['charset'])) { $params['defaultTableOptions']['charset'] = $params['charset']; } $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']); return $schemaConfig; } /** * The search path for namespaces in the currently connected database. * * The first entry is usually the default namespace in the Schema. All * further namespaces contain tables/sequences which can also be addressed * with a short, not full-qualified name. * * For databases that don't support subschema/namespaces this method * returns the name of the currently connected database. * * @deprecated * * @return string[] * * @throws Exception */ public function getSchemaSearchPaths() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4821', 'AbstractSchemaManager::getSchemaSearchPaths() is deprecated.', ); $database = $this->_conn->getDatabase(); if ($database !== null) { return [$database]; } return []; } /** * Given a table comment this method tries to extract a typehint for Doctrine Type, or returns * the type given as default. * * @internal This method should be only used from within the AbstractSchemaManager class hierarchy. * * @param string|null $comment * @param string $currentType * * @return string */ public function extractDoctrineTypeFromComment($comment, $currentType) { if ($this->_conn->getConfiguration()->getDisableTypeComments()) { return $currentType; } if ($comment !== null && preg_match('(\(DC2Type:(((?!\)).)+)\))', $comment, $match) === 1) { return $match[1]; } return $currentType; } /** * @internal This method should be only used from within the AbstractSchemaManager class hierarchy. * * @param string|null $comment * @param string|null $type * * @return string|null */ public function removeDoctrineTypeFromComment($comment, $type) { if ($this->_conn->getConfiguration()->getDisableTypeComments()) { return $comment; } if ($comment === null) { return null; } return str_replace('(DC2Type:' . $type . ')', '', $comment); } /** @throws Exception */ private function getDatabase(string $methodName): string { $database = $this->_conn->getDatabase(); if ($database === null) { throw DatabaseRequired::new($methodName); } return $database; } public function createComparator(): Comparator { return new Comparator($this->_platform); } /** * @return array<string,list<array<string,mixed>>> * * @throws Exception */ private function fetchAllAssociativeGrouped(Result $result): array { $data = []; foreach ($result->fetchAllAssociative() as $row) { $tableName = $this->_getPortableTableDefinition($row); $data[$tableName][] = $row; } return $data; } } dbal/src/Schema/OracleSchemaManager.php 0000644 00000040061 15021222234 0013761 0 ustar 00 <?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} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ 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); } } dbal/src/Schema/PostgreSQLSchemaManager.php 0000644 00000056115 15021222234 0014566 0 ustar 00 <?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} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ 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; } } dbal/src/Schema/SchemaDiff.php 0000644 00000017036 15021222234 0012137 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function array_filter; use function array_merge; use function count; /** * Differences between two schemas. * * The object contains the operations to change the schema stored in $fromSchema * to a target schema. */ class SchemaDiff { /** * @deprecated * * @var Schema|null */ public $fromSchema; /** * All added namespaces. * * @internal Use {@link getCreatedSchemas()} instead. * * @var string[] */ public $newNamespaces = []; /** * All removed namespaces. * * @internal Use {@link getDroppedSchemas()} instead. * * @var string[] */ public $removedNamespaces = []; /** * All added tables. * * @internal Use {@link getCreatedTables()} instead. * * @var Table[] */ public $newTables = []; /** * All changed tables. * * @internal Use {@link getAlteredTables()} instead. * * @var TableDiff[] */ public $changedTables = []; /** * All removed tables. * * @internal Use {@link getDroppedTables()} instead. * * @var Table[] */ public $removedTables = []; /** * @internal Use {@link getCreatedSequences()} instead. * * @var Sequence[] */ public $newSequences = []; /** * @internal Use {@link getAlteredSequences()} instead. * * @var Sequence[] */ public $changedSequences = []; /** * @internal Use {@link getDroppedSequences()} instead. * * @var Sequence[] */ public $removedSequences = []; /** * @deprecated * * @var ForeignKeyConstraint[] */ public $orphanedForeignKeys = []; /** * Constructs an SchemaDiff object. * * @internal The diff can be only instantiated by a {@see Comparator}. * * @param Table[] $newTables * @param TableDiff[] $changedTables * @param Table[] $removedTables * @param array<string> $createdSchemas * @param array<string> $droppedSchemas * @param array<Sequence> $createdSequences * @param array<Sequence> $alteredSequences * @param array<Sequence> $droppedSequences */ public function __construct( $newTables = [], $changedTables = [], $removedTables = [], ?Schema $fromSchema = null, $createdSchemas = [], $droppedSchemas = [], $createdSequences = [], $alteredSequences = [], $droppedSequences = [] ) { $this->newTables = $newTables; $this->changedTables = array_filter($changedTables, static function (TableDiff $diff): bool { return ! $diff->isEmpty(); }); $this->removedTables = $removedTables; $this->fromSchema = $fromSchema; $this->newNamespaces = $createdSchemas; $this->removedNamespaces = $droppedSchemas; $this->newSequences = $createdSequences; $this->changedSequences = $alteredSequences; $this->removedSequences = $droppedSequences; } /** @return array<string> */ public function getCreatedSchemas(): array { return $this->newNamespaces; } /** @return array<string> */ public function getDroppedSchemas(): array { return $this->removedNamespaces; } /** @return array<Table> */ public function getCreatedTables(): array { return $this->newTables; } /** @return array<TableDiff> */ public function getAlteredTables(): array { return $this->changedTables; } /** @return array<Table> */ public function getDroppedTables(): array { return $this->removedTables; } /** @return array<Sequence> */ public function getCreatedSequences(): array { return $this->newSequences; } /** @return array<Sequence> */ public function getAlteredSequences(): array { return $this->changedSequences; } /** @return array<Sequence> */ public function getDroppedSequences(): array { return $this->removedSequences; } /** * Returns whether the diff is empty (contains no changes). */ public function isEmpty(): bool { return count($this->newNamespaces) === 0 && count($this->removedNamespaces) === 0 && count($this->newTables) === 0 && count($this->changedTables) === 0 && count($this->removedTables) === 0 && count($this->newSequences) === 0 && count($this->changedSequences) === 0 && count($this->removedSequences) === 0; } /** * The to save sql mode ensures that the following things don't happen: * * 1. Tables are deleted * 2. Sequences are deleted * 3. Foreign Keys which reference tables that would otherwise be deleted. * * This way it is ensured that assets are deleted which might not be relevant to the metadata schema at all. * * @deprecated * * @return list<string> */ public function toSaveSql(AbstractPlatform $platform) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5766', '%s is deprecated.', __METHOD__, ); return $this->_toSql($platform, true); } /** * @deprecated Use {@link AbstractPlatform::getAlterSchemaSQL()} instead. * * @return list<string> */ public function toSql(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5766', '%s is deprecated. Use AbstractPlatform::getAlterSchemaSQL() instead.', __METHOD__, ); return $this->_toSql($platform, false); } /** * @param bool $saveMode * * @return list<string> */ protected function _toSql(AbstractPlatform $platform, $saveMode = false) { $sql = []; if ($platform->supportsSchemas()) { foreach ($this->getCreatedSchemas() as $schema) { $sql[] = $platform->getCreateSchemaSQL($schema); } } if ($platform->supportsForeignKeyConstraints() && $saveMode === false) { foreach ($this->orphanedForeignKeys as $orphanedForeignKey) { $sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTable()); } } if ($platform->supportsSequences() === true) { foreach ($this->getAlteredSequences() as $sequence) { $sql[] = $platform->getAlterSequenceSQL($sequence); } if ($saveMode === false) { foreach ($this->getDroppedSequences() as $sequence) { $sql[] = $platform->getDropSequenceSQL($sequence); } } foreach ($this->getCreatedSequences() as $sequence) { $sql[] = $platform->getCreateSequenceSQL($sequence); } } $sql = array_merge($sql, $platform->getCreateTablesSQL($this->getCreatedTables())); if ($saveMode === false) { $sql = array_merge($sql, $platform->getDropTablesSQL($this->getDroppedTables())); } foreach ($this->getAlteredTables() as $tableDiff) { $sql = array_merge($sql, $platform->getAlterTableSQL($tableDiff)); } return $sql; } } dbal/src/Schema/TableDiff.php 0000644 00000023161 15021222234 0011762 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function array_filter; use function array_values; use function count; /** * Table Diff. */ class TableDiff { /** * @deprecated Use {@see getOldTable()} instead. * * @var string */ public $name; /** * @deprecated Rename tables via {@link AbstractSchemaManager::renameTable()} instead. * * @var string|false */ public $newName = false; /** * All added columns * * @internal Use {@see getAddedColumns()} instead. * * @var Column[] */ public $addedColumns; /** * All modified columns * * @internal Use {@see getModifiedColumns()} instead. * * @var ColumnDiff[] */ public $changedColumns = []; /** * All dropped columns * * @internal Use {@see getDroppedColumns()} instead. * * @var Column[] */ public $removedColumns = []; /** * Columns that are only renamed from key to column instance name. * * @internal Use {@see getRenamedColumns()} instead. * * @var Column[] */ public $renamedColumns = []; /** * All added indexes. * * @internal Use {@see getAddedIndexes()} instead. * * @var Index[] */ public $addedIndexes = []; /** * All changed indexes. * * @internal Use {@see getModifiedIndexes()} instead. * * @var Index[] */ public $changedIndexes = []; /** * All removed indexes * * @internal Use {@see getDroppedIndexes()} instead. * * @var Index[] */ public $removedIndexes = []; /** * Indexes that are only renamed but are identical otherwise. * * @internal Use {@see getRenamedIndexes()} instead. * * @var Index[] */ public $renamedIndexes = []; /** * All added foreign key definitions * * @internal Use {@see getAddedForeignKeys()} instead. * * @var ForeignKeyConstraint[] */ public $addedForeignKeys = []; /** * All changed foreign keys * * @internal Use {@see getModifiedForeignKeys()} instead. * * @var ForeignKeyConstraint[] */ public $changedForeignKeys = []; /** * All removed foreign keys * * @internal Use {@see getDroppedForeignKeys()} instead. * * @var (ForeignKeyConstraint|string)[] */ public $removedForeignKeys = []; /** * @internal Use {@see getOldTable()} instead. * * @var Table|null */ public $fromTable; /** * Constructs a TableDiff object. * * @internal The diff can be only instantiated by a {@see Comparator}. * * @param string $tableName * @param array<Column> $addedColumns * @param array<ColumnDiff> $modifiedColumns * @param array<Column> $droppedColumns * @param array<Index> $addedIndexes * @param array<Index> $changedIndexes * @param array<Index> $removedIndexes * @param list<ForeignKeyConstraint> $addedForeignKeys * @param list<ForeignKeyConstraint> $changedForeignKeys * @param list<ForeignKeyConstraint|string> $removedForeignKeys * @param array<string,Column> $renamedColumns * @param array<string,Index> $renamedIndexes */ public function __construct( $tableName, $addedColumns = [], $modifiedColumns = [], $droppedColumns = [], $addedIndexes = [], $changedIndexes = [], $removedIndexes = [], ?Table $fromTable = null, $addedForeignKeys = [], $changedForeignKeys = [], $removedForeignKeys = [], $renamedColumns = [], $renamedIndexes = [] ) { $this->name = $tableName; $this->addedColumns = $addedColumns; $this->changedColumns = $modifiedColumns; $this->renamedColumns = $renamedColumns; $this->removedColumns = $droppedColumns; $this->addedIndexes = $addedIndexes; $this->changedIndexes = $changedIndexes; $this->renamedIndexes = $renamedIndexes; $this->removedIndexes = $removedIndexes; $this->addedForeignKeys = $addedForeignKeys; $this->changedForeignKeys = $changedForeignKeys; $this->removedForeignKeys = $removedForeignKeys; if ($fromTable === null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5678', 'Not passing the $fromTable to %s is deprecated.', __METHOD__, ); } $this->fromTable = $fromTable; } /** * @deprecated Use {@see getOldTable()} instead. * * @param AbstractPlatform $platform The platform to use for retrieving this table diff's name. * * @return Identifier */ public function getName(AbstractPlatform $platform) { return new Identifier( $this->fromTable instanceof Table ? $this->fromTable->getQuotedName($platform) : $this->name, ); } /** * @deprecated Rename tables via {@link AbstractSchemaManager::renameTable()} instead. * * @return Identifier|false */ public function getNewName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', '%s is deprecated. Rename tables via AbstractSchemaManager::renameTable() instead.', __METHOD__, ); if ($this->newName === false) { return false; } return new Identifier($this->newName); } public function getOldTable(): ?Table { return $this->fromTable; } /** @return list<Column> */ public function getAddedColumns(): array { return array_values($this->addedColumns); } /** @return list<ColumnDiff> */ public function getModifiedColumns(): array { return array_values($this->changedColumns); } /** @return list<Column> */ public function getDroppedColumns(): array { return array_values($this->removedColumns); } /** @return array<string,Column> */ public function getRenamedColumns(): array { return $this->renamedColumns; } /** @return list<Index> */ public function getAddedIndexes(): array { return array_values($this->addedIndexes); } /** * @internal This method exists only for compatibility with the current implementation of schema managers * that modify the diff while processing it. */ public function unsetAddedIndex(Index $index): void { $this->addedIndexes = array_filter( $this->addedIndexes, static function (Index $addedIndex) use ($index): bool { return $addedIndex !== $index; }, ); } /** @return array<Index> */ public function getModifiedIndexes(): array { return array_values($this->changedIndexes); } /** @return list<Index> */ public function getDroppedIndexes(): array { return array_values($this->removedIndexes); } /** * @internal This method exists only for compatibility with the current implementation of schema managers * that modify the diff while processing it. */ public function unsetDroppedIndex(Index $index): void { $this->removedIndexes = array_filter( $this->removedIndexes, static function (Index $removedIndex) use ($index): bool { return $removedIndex !== $index; }, ); } /** @return array<string,Index> */ public function getRenamedIndexes(): array { return $this->renamedIndexes; } /** @return list<ForeignKeyConstraint> */ public function getAddedForeignKeys(): array { return $this->addedForeignKeys; } /** @return list<ForeignKeyConstraint> */ public function getModifiedForeignKeys(): array { return $this->changedForeignKeys; } /** @return list<ForeignKeyConstraint|string> */ public function getDroppedForeignKeys(): array { return $this->removedForeignKeys; } /** * @internal This method exists only for compatibility with the current implementation of the schema comparator. * * @param ForeignKeyConstraint|string $foreignKey */ public function unsetDroppedForeignKey($foreignKey): void { $this->removedForeignKeys = array_filter( $this->removedForeignKeys, static function ($removedForeignKey) use ($foreignKey): bool { return $removedForeignKey !== $foreignKey; }, ); } /** * Returns whether the diff is empty (contains no changes). */ public function isEmpty(): bool { return count($this->addedColumns) === 0 && count($this->changedColumns) === 0 && count($this->removedColumns) === 0 && count($this->renamedColumns) === 0 && count($this->addedIndexes) === 0 && count($this->changedIndexes) === 0 && count($this->removedIndexes) === 0 && count($this->renamedIndexes) === 0 && count($this->addedForeignKeys) === 0 && count($this->changedForeignKeys) === 0 && count($this->removedForeignKeys) === 0; } } dbal/src/Schema/SchemaManagerFactory.php 0000644 00000000575 15021222234 0014171 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Connection; /** * Creates a schema manager for the given connection. * * This interface is an extension point for applications that need to override schema managers. */ interface SchemaManagerFactory { public function createSchemaManager(Connection $connection): AbstractSchemaManager; } dbal/src/Schema/SchemaConfig.php 0000644 00000004633 15021222234 0012473 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\Deprecations\Deprecation; /** * Configuration for a Schema. */ class SchemaConfig { /** * @deprecated * * @var bool */ protected $hasExplicitForeignKeyIndexes = false; /** @var int */ protected $maxIdentifierLength = 63; /** @var string|null */ protected $name; /** @var mixed[] */ protected $defaultTableOptions = []; /** * @deprecated * * @return bool */ public function hasExplicitForeignKeyIndexes() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4822', 'SchemaConfig::hasExplicitForeignKeyIndexes() is deprecated.', ); return $this->hasExplicitForeignKeyIndexes; } /** * @deprecated * * @param bool $flag * * @return void */ public function setExplicitForeignKeyIndexes($flag) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4822', 'SchemaConfig::setExplicitForeignKeyIndexes() is deprecated.', ); $this->hasExplicitForeignKeyIndexes = (bool) $flag; } /** * @param int $length * * @return void */ public function setMaxIdentifierLength($length) { $this->maxIdentifierLength = (int) $length; } /** @return int */ public function getMaxIdentifierLength() { return $this->maxIdentifierLength; } /** * Gets the default namespace of schema objects. * * @return string|null */ public function getName() { return $this->name; } /** * Sets the default namespace name of schema objects. * * @param string $name The value to set. * * @return void */ public function setName($name) { $this->name = $name; } /** * Gets the default options that are passed to Table instances created with * Schema#createTable(). * * @return mixed[] */ public function getDefaultTableOptions() { return $this->defaultTableOptions; } /** * @param mixed[] $defaultTableOptions * * @return void */ public function setDefaultTableOptions(array $defaultTableOptions) { $this->defaultTableOptions = $defaultTableOptions; } } dbal/src/Schema/DB2SchemaManager.php 0000644 00000031046 15021222234 0013126 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use function array_change_key_case; use function implode; use function preg_match; use function str_replace; use function strpos; use function strtolower; use function strtoupper; use function substr; use const CASE_LOWER; /** * IBM Db2 Schema Manager. * * @extends AbstractSchemaManager<DB2Platform> */ class DB2SchemaManager 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} * * @throws Exception */ protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); $length = null; $fixed = null; $scale = false; $precision = false; $default = null; if ($tableColumn['default'] !== null && $tableColumn['default'] !== 'NULL') { $default = $tableColumn['default']; if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) { $default = str_replace("''", "'", $matches[1]); } } $type = $this->_platform->getDoctrineTypeMapping($tableColumn['typename']); if (isset($tableColumn['comment'])) { $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); } switch (strtolower($tableColumn['typename'])) { case 'varchar': if ($tableColumn['codepage'] === 0) { $type = Types::BINARY; } $length = $tableColumn['length']; $fixed = false; break; case 'character': if ($tableColumn['codepage'] === 0) { $type = Types::BINARY; } $length = $tableColumn['length']; $fixed = true; break; case 'clob': $length = $tableColumn['length']; break; case 'decimal': case 'double': case 'real': $scale = $tableColumn['scale']; $precision = $tableColumn['length']; break; } $options = [ 'length' => $length, 'fixed' => (bool) $fixed, 'default' => $default, 'autoincrement' => (bool) $tableColumn['autoincrement'], 'notnull' => $tableColumn['nulls'] === 'N', 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, ]; if ($scale !== null && $precision !== null) { $options['scale'] = $scale; $options['precision'] = $precision; } return new Column($tableColumn['colname'], Type::getType($type), $options); } /** * {@inheritDoc} */ protected function _getPortableTableDefinition($table) { $table = array_change_key_case($table, CASE_LOWER); return $table['name']; } /** * {@inheritDoc} */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { foreach ($tableIndexes as &$tableIndexRow) { $tableIndexRow = array_change_key_case($tableIndexRow, CASE_LOWER); $tableIndexRow['primary'] = (bool) $tableIndexRow['primary']; } return parent::_getPortableTableIndexesList($tableIndexes, $tableName); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { return new ForeignKeyConstraint( $tableForeignKey['local_columns'], $tableForeignKey['foreign_table'], $tableForeignKey['foreign_columns'], $tableForeignKey['name'], $tableForeignKey['options'], ); } /** * {@inheritDoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $foreignKeys = []; foreach ($tableForeignKeys as $tableForeignKey) { $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); if (! isset($foreignKeys[$tableForeignKey['index_name']])) { $foreignKeys[$tableForeignKey['index_name']] = [ 'local_columns' => [$tableForeignKey['local_column']], 'foreign_table' => $tableForeignKey['foreign_table'], 'foreign_columns' => [$tableForeignKey['foreign_column']], 'name' => $tableForeignKey['index_name'], 'options' => [ 'onUpdate' => $tableForeignKey['on_update'], 'onDelete' => $tableForeignKey['on_delete'], ], ]; } else { $foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column']; $foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column']; } } return parent::_getPortableTableForeignKeysList($foreignKeys); } /** * @param string $def * * @return string|null */ protected function _getPortableForeignKeyRuleDef($def) { if ($def === 'C') { return 'CASCADE'; } if ($def === 'N') { return 'SET NULL'; } return null; } /** * {@inheritDoc} */ protected function _getPortableViewDefinition($view) { $view = array_change_key_case($view, CASE_LOWER); $sql = ''; $pos = strpos($view['text'], ' AS '); if ($pos !== false) { $sql = substr($view['text'], $pos + 4); } return new View($view['name'], $sql); } protected function normalizeName(string $name): string { $identifier = new Identifier($name); return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name); } protected function selectTableNames(string $databaseName): Result { $sql = <<<'SQL' SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T' AND CREATOR = ? SQL; return $this->_conn->executeQuery($sql, [$databaseName]); } protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' C.TABNAME AS NAME,'; } $sql .= <<<'SQL' C.COLNAME, C.TYPENAME, C.CODEPAGE, C.NULLS, C.LENGTH, C.SCALE, C.REMARKS AS COMMENT, CASE WHEN C.GENERATED = 'D' THEN 1 ELSE 0 END AS AUTOINCREMENT, C.DEFAULT FROM SYSCAT.COLUMNS C JOIN SYSCAT.TABLES AS T ON T.TABSCHEMA = C.TABSCHEMA AND T.TABNAME = C.TABNAME SQL; $conditions = ['C.TABSCHEMA = ?', "T.TYPE = 'T'"]; $params = [$databaseName]; if ($tableName !== null) { $conditions[] = 'C.TABNAME = ?'; $params[] = $tableName; } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.TABNAME, C.COLNO'; return $this->_conn->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' IDX.TABNAME AS NAME,'; } $sql .= <<<'SQL' IDX.INDNAME AS KEY_NAME, IDXCOL.COLNAME AS COLUMN_NAME, CASE WHEN IDX.UNIQUERULE = 'P' THEN 1 ELSE 0 END AS PRIMARY, CASE WHEN IDX.UNIQUERULE = 'D' THEN 1 ELSE 0 END AS NON_UNIQUE FROM SYSCAT.INDEXES AS IDX JOIN SYSCAT.TABLES AS T ON IDX.TABSCHEMA = T.TABSCHEMA AND IDX.TABNAME = T.TABNAME JOIN SYSCAT.INDEXCOLUSE AS IDXCOL ON IDX.INDSCHEMA = IDXCOL.INDSCHEMA AND IDX.INDNAME = IDXCOL.INDNAME SQL; $conditions = ['IDX.TABSCHEMA = ?', "T.TYPE = 'T'"]; $params = [$databaseName]; if ($tableName !== null) { $conditions[] = 'IDX.TABNAME = ?'; $params[] = $tableName; } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IDX.INDNAME, IDXCOL.COLSEQ'; return $this->_conn->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { $sql = 'SELECT'; if ($tableName === null) { $sql .= ' R.TABNAME AS NAME,'; } $sql .= <<<'SQL' FKCOL.COLNAME AS LOCAL_COLUMN, R.REFTABNAME AS FOREIGN_TABLE, PKCOL.COLNAME AS FOREIGN_COLUMN, R.CONSTNAME AS INDEX_NAME, CASE WHEN R.UPDATERULE = 'R' THEN 'RESTRICT' END AS ON_UPDATE, CASE WHEN R.DELETERULE = 'C' THEN 'CASCADE' WHEN R.DELETERULE = 'N' THEN 'SET NULL' WHEN R.DELETERULE = 'R' THEN 'RESTRICT' END AS ON_DELETE FROM SYSCAT.REFERENCES AS R JOIN SYSCAT.TABLES AS T ON T.TABSCHEMA = R.TABSCHEMA AND T.TABNAME = R.TABNAME JOIN SYSCAT.KEYCOLUSE AS FKCOL ON FKCOL.CONSTNAME = R.CONSTNAME AND FKCOL.TABSCHEMA = R.TABSCHEMA AND FKCOL.TABNAME = R.TABNAME JOIN SYSCAT.KEYCOLUSE AS PKCOL ON PKCOL.CONSTNAME = R.REFKEYNAME AND PKCOL.TABSCHEMA = R.REFTABSCHEMA AND PKCOL.TABNAME = R.REFTABNAME AND PKCOL.COLSEQ = FKCOL.COLSEQ SQL; $conditions = ['R.TABSCHEMA = ?', "T.TYPE = 'T'"]; $params = [$databaseName]; if ($tableName !== null) { $conditions[] = 'R.TABNAME = ?'; $params[] = $tableName; } $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY R.CONSTNAME, FKCOL.COLSEQ'; return $this->_conn->executeQuery($sql, $params); } /** * {@inheritDoc} */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { $sql = 'SELECT NAME, REMARKS'; $conditions = []; $params = []; if ($tableName !== null) { $conditions[] = 'NAME = ?'; $params[] = $tableName; } $sql .= ' FROM SYSIBM.SYSTABLES'; if ($conditions !== []) { $sql .= ' 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['remarks']]; } return $tableOptions; } } dbal/src/Schema/Exception/IndexAlreadyExists.php 0000644 00000000747 15021222234 0015656 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class IndexAlreadyExists extends SchemaException { public static function new(string $indexName, string $table): self { return new self( sprintf('An index with name "%s" was already defined on table "%s".', $indexName, $table), self::INDEX_ALREADY_EXISTS, ); } } dbal/src/Schema/Exception/ForeignKeyDoesNotExist.php 0000644 00000000773 15021222234 0016457 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class ForeignKeyDoesNotExist extends SchemaException { public static function new(string $foreignKeyName, string $table): self { return new self( sprintf('There exists no foreign key with the name "%s" on table "%s".', $foreignKeyName, $table), self::FOREIGNKEY_DOESNT_EXIST, ); } } dbal/src/Schema/Exception/NamedForeignKeyRequired.php 0000644 00000001624 15021222234 0016610 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Table; use function implode; use function sprintf; /** @psalm-immutable */ final class NamedForeignKeyRequired extends SchemaException { public static function new(Table $localTable, ForeignKeyConstraint $foreignKey): self { return new self( sprintf( 'The performed schema operation on "%s" requires a named foreign key, ' . 'but the given foreign key from (%s) onto foreign table "%s" (%s) is currently unnamed.', $localTable->getName(), implode(', ', $foreignKey->getColumns()), $foreignKey->getForeignTableName(), implode(', ', $foreignKey->getForeignColumns()), ), ); } } dbal/src/Schema/Exception/UnknownColumnOption.php 0000644 00000000615 15021222234 0016105 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class UnknownColumnOption extends SchemaException { public static function new(string $name): self { return new self( sprintf('The "%s" column option is not supported.', $name), ); } } dbal/src/Schema/Exception/IndexNameInvalid.php 0000644 00000000710 15021222234 0015252 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class IndexNameInvalid extends SchemaException { public static function new(string $indexName): self { return new self( sprintf('Invalid index name "%s" given, has to be [a-zA-Z0-9_].', $indexName), self::INDEX_INVALID_NAME, ); } } dbal/src/Schema/Exception/NamespaceAlreadyExists.php 0000644 00000000722 15021222234 0016474 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class NamespaceAlreadyExists extends SchemaException { public static function new(string $namespaceName): self { return new self( sprintf('The namespace with name "%s" already exists.', $namespaceName), self::NAMESPACE_ALREADY_EXISTS, ); } } dbal/src/Schema/Exception/ColumnDoesNotExist.php 0000644 00000000736 15021222234 0015651 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class ColumnDoesNotExist extends SchemaException { public static function new(string $columnName, string $table): self { return new self( sprintf('There is no column with name "%s" on table "%s".', $columnName, $table), self::COLUMN_DOESNT_EXIST, ); } } dbal/src/Schema/Exception/ColumnAlreadyExists.php 0000644 00000000746 15021222234 0016043 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class ColumnAlreadyExists extends SchemaException { public static function new(string $tableName, string $columnName): self { return new self( sprintf('The column "%s" on table "%s" already exists.', $columnName, $tableName), self::COLUMN_ALREADY_EXISTS, ); } } dbal/src/Schema/Exception/IndexDoesNotExist.php 0000644 00000000722 15021222234 0015456 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class IndexDoesNotExist extends SchemaException { public static function new(string $indexName, string $table): self { return new self( sprintf('Index "%s" does not exist on table "%s".', $indexName, $table), self::INDEX_DOESNT_EXIST, ); } } dbal/src/Schema/Exception/TableAlreadyExists.php 0000644 00000000676 15021222234 0015637 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class TableAlreadyExists extends SchemaException { public static function new(string $tableName): self { return new self( sprintf('The table with name "%s" already exists.', $tableName), self::TABLE_ALREADY_EXISTS, ); } } dbal/src/Schema/Exception/UniqueConstraintDoesNotExist.php 0000644 00000001007 15021222234 0017717 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class UniqueConstraintDoesNotExist extends SchemaException { public static function new(string $constraintName, string $table): self { return new self( sprintf('There exists no unique constraint with the name "%s" on table "%s".', $constraintName, $table), self::CONSTRAINT_DOESNT_EXIST, ); } } dbal/src/Schema/Exception/SequenceAlreadyExists.php 0000644 00000000703 15021222234 0016347 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class SequenceAlreadyExists extends SchemaException { public static function new(string $sequenceName): self { return new self( sprintf('The sequence "%s" already exists.', $sequenceName), self::SEQUENCE_ALREADY_EXISTS, ); } } dbal/src/Schema/Exception/TableDoesNotExist.php 0000644 00000000702 15021222234 0015434 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class TableDoesNotExist extends SchemaException { public static function new(string $tableName): self { return new self( sprintf('There is no table with name "%s" in the schema.', $tableName), self::TABLE_DOESNT_EXIST, ); } } dbal/src/Schema/Exception/InvalidTableName.php 0000644 00000000567 15021222234 0015244 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class InvalidTableName extends SchemaException { public static function new(string $tableName): self { return new self(sprintf('Invalid table name specified "%s".', $tableName)); } } dbal/src/Schema/Exception/SequenceDoesNotExist.php 0000644 00000000713 15021222234 0016157 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema\Exception; use Doctrine\DBAL\Schema\SchemaException; use function sprintf; /** @psalm-immutable */ final class SequenceDoesNotExist extends SchemaException { public static function new(string $sequenceName): self { return new self( sprintf('There exists no sequence with the name "%s".', $sequenceName), self::SEQUENCE_DOENST_EXIST, ); } } dbal/src/Schema/SchemaException.php 0000644 00000012647 15021222234 0013230 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\Exception\ColumnAlreadyExists; use Doctrine\DBAL\Schema\Exception\ColumnDoesNotExist; use Doctrine\DBAL\Schema\Exception\ForeignKeyDoesNotExist; use Doctrine\DBAL\Schema\Exception\IndexAlreadyExists; use Doctrine\DBAL\Schema\Exception\IndexDoesNotExist; use Doctrine\DBAL\Schema\Exception\IndexNameInvalid; use Doctrine\DBAL\Schema\Exception\NamedForeignKeyRequired; use Doctrine\DBAL\Schema\Exception\NamespaceAlreadyExists; use Doctrine\DBAL\Schema\Exception\SequenceAlreadyExists; use Doctrine\DBAL\Schema\Exception\SequenceDoesNotExist; use Doctrine\DBAL\Schema\Exception\TableAlreadyExists; use Doctrine\DBAL\Schema\Exception\TableDoesNotExist; use Doctrine\DBAL\Schema\Exception\UniqueConstraintDoesNotExist; use function sprintf; /** @psalm-immutable */ class SchemaException extends Exception { /** @deprecated Use {@see TableDoesNotExist} instead. */ public const TABLE_DOESNT_EXIST = 10; /** @deprecated Use {@see TableAlreadyExists} instead. */ public const TABLE_ALREADY_EXISTS = 20; /** @deprecated Use {@see ColumnDoesNotExist} instead. */ public const COLUMN_DOESNT_EXIST = 30; /** @deprecated Use {@see ColumnAlreadyExists} instead. */ public const COLUMN_ALREADY_EXISTS = 40; /** @deprecated Use {@see IndexDoesNotExist} instead. */ public const INDEX_DOESNT_EXIST = 50; /** @deprecated Use {@see IndexAlreadyExists} instead. */ public const INDEX_ALREADY_EXISTS = 60; /** @deprecated Use {@see SequenceDoesNotExist} instead. */ public const SEQUENCE_DOENST_EXIST = 70; /** @deprecated Use {@see SequenceAlreadyExists} instead. */ public const SEQUENCE_ALREADY_EXISTS = 80; /** @deprecated Use {@see IndexNameInvalid} instead. */ public const INDEX_INVALID_NAME = 90; /** @deprecated Use {@see ForeignKeyDoesNotExist} instead. */ public const FOREIGNKEY_DOESNT_EXIST = 100; /** @deprecated Use {@see UniqueConstraintDoesNotExist} instead. */ public const CONSTRAINT_DOESNT_EXIST = 110; /** @deprecated Use {@see NamespaceAlreadyExists} instead. */ public const NAMESPACE_ALREADY_EXISTS = 120; /** * @param string $tableName * * @return SchemaException */ public static function tableDoesNotExist($tableName) { return TableDoesNotExist::new($tableName); } /** * @param string $indexName * * @return SchemaException */ public static function indexNameInvalid($indexName) { return IndexNameInvalid::new($indexName); } /** * @param string $indexName * @param string $table * * @return SchemaException */ public static function indexDoesNotExist($indexName, $table) { return IndexDoesNotExist::new($indexName, $table); } /** * @param string $indexName * @param string $table * * @return SchemaException */ public static function indexAlreadyExists($indexName, $table) { return IndexAlreadyExists::new($indexName, $table); } /** * @param string $columnName * @param string $table * * @return SchemaException */ public static function columnDoesNotExist($columnName, $table) { return ColumnDoesNotExist::new($columnName, $table); } /** * @param string $namespaceName * * @return SchemaException */ public static function namespaceAlreadyExists($namespaceName) { return NamespaceAlreadyExists::new($namespaceName); } /** * @param string $tableName * * @return SchemaException */ public static function tableAlreadyExists($tableName) { return TableAlreadyExists::new($tableName); } /** * @param string $tableName * @param string $columnName * * @return SchemaException */ public static function columnAlreadyExists($tableName, $columnName) { return ColumnAlreadyExists::new($tableName, $columnName); } /** * @param string $name * * @return SchemaException */ public static function sequenceAlreadyExists($name) { return SequenceAlreadyExists::new($name); } /** * @param string $name * * @return SchemaException */ public static function sequenceDoesNotExist($name) { return SequenceDoesNotExist::new($name); } /** * @param string $constraintName * @param string $table * * @return SchemaException */ public static function uniqueConstraintDoesNotExist($constraintName, $table) { return UniqueConstraintDoesNotExist::new($constraintName, $table); } /** * @param string $fkName * @param string $table * * @return SchemaException */ public static function foreignKeyDoesNotExist($fkName, $table) { return ForeignKeyDoesNotExist::new($fkName, $table); } /** @return SchemaException */ public static function namedForeignKeyRequired(Table $localTable, ForeignKeyConstraint $foreignKey) { return NamedForeignKeyRequired::new($localTable, $foreignKey); } /** * @param string $changeName * * @return SchemaException */ public static function alterTableChangeNotSupported($changeName) { return new self( sprintf("Alter table change not supported, given '%s'", $changeName), ); } } dbal/src/Schema/View.php 0000644 00000000675 15021222234 0011061 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; /** * Representation of a Database View. */ class View extends AbstractAsset { /** @var string */ private $sql; /** * @param string $name * @param string $sql */ public function __construct($name, $sql) { $this->_setName($name); $this->sql = $sql; } /** @return string */ public function getSql() { return $this->sql; } } dbal/src/Schema/ColumnDiff.php 0000644 00000010214 15021222234 0012163 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\Deprecations\Deprecation; use function in_array; /** * Represents the change of a column. */ class ColumnDiff { /** * @deprecated Use {@see $fromColumn} and {@see Column::getName()} instead. * * @var string */ public $oldColumnName; /** * @internal Use {@see getNewColumn()} instead. * * @var Column */ public $column; /** * @deprecated Use {@see hasTypeChanged()}, {@see hasLengthChanged()}, {@see hasPrecisionChanged()}, * {@see hasScaleChanged()}, {@see hasUnsignedChanged()}, {@see hasFixedChanged()}, {@see hasNotNullChanged()}, * {@see hasDefaultChanged()}, {@see hasAutoIncrementChanged()} or {@see hasCommentChanged()} instead. * * @var string[] */ public $changedProperties = []; /** * @internal Use {@see getOldColumn()} instead. * * @var Column|null */ public $fromColumn; /** * @internal The diff can be only instantiated by a {@see Comparator}. * * @param string $oldColumnName * @param string[] $changedProperties */ public function __construct( $oldColumnName, Column $column, array $changedProperties = [], ?Column $fromColumn = null ) { if ($fromColumn === null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4785', 'Not passing the $fromColumn to %s is deprecated.', __METHOD__, ); } $this->oldColumnName = $oldColumnName; $this->column = $column; $this->changedProperties = $changedProperties; $this->fromColumn = $fromColumn; } public function getOldColumn(): ?Column { return $this->fromColumn; } public function getNewColumn(): Column { return $this->column; } public function hasTypeChanged(): bool { return $this->hasChanged('type'); } public function hasLengthChanged(): bool { return $this->hasChanged('length'); } public function hasPrecisionChanged(): bool { return $this->hasChanged('precision'); } public function hasScaleChanged(): bool { return $this->hasChanged('scale'); } public function hasUnsignedChanged(): bool { return $this->hasChanged('unsigned'); } public function hasFixedChanged(): bool { return $this->hasChanged('fixed'); } public function hasNotNullChanged(): bool { return $this->hasChanged('notnull'); } public function hasDefaultChanged(): bool { return $this->hasChanged('default'); } public function hasAutoIncrementChanged(): bool { return $this->hasChanged('autoincrement'); } public function hasCommentChanged(): bool { return $this->hasChanged('comment'); } /** * @deprecated Use {@see hasTypeChanged()}, {@see hasLengthChanged()}, {@see hasPrecisionChanged()}, * {@see hasScaleChanged()}, {@see hasUnsignedChanged()}, {@see hasFixedChanged()}, {@see hasNotNullChanged()}, * {@see hasDefaultChanged()}, {@see hasAutoIncrementChanged()} or {@see hasCommentChanged()} instead. * * @param string $propertyName * * @return bool */ public function hasChanged($propertyName) { return in_array($propertyName, $this->changedProperties, true); } /** * @deprecated Use {@see $fromColumn} instead. * * @return Identifier */ public function getOldColumnName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5622', '%s is deprecated. Use $fromColumn instead.', __METHOD__, ); if ($this->fromColumn !== null) { $name = $this->fromColumn->getName(); $quote = $this->fromColumn->isQuoted(); } else { $name = $this->oldColumnName; $quote = false; } return new Identifier($name, $quote); } } dbal/src/Schema/Visitor/RemoveNamespacedAssets.php 0000644 00000005372 15021222234 0016206 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\Deprecations\Deprecation; /** * Removes assets from a schema that are not in the default namespace. * * Some databases such as MySQL support cross databases joins, but don't * allow to call DDLs to a database from another connected database. * Before a schema is serialized into SQL this visitor can cleanup schemas with * non default namespaces. * * This visitor filters all these non-default namespaced tables and sequences * and removes them from the Schema instance. * * @deprecated Do not use namespaces if the target database platform doesn't support them. */ class RemoveNamespacedAssets extends AbstractVisitor { private ?Schema $schema = null; public function __construct() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5432', 'RemoveNamespacedAssets is deprecated. Do not use namespaces' . " if the target database platform doesn't support them.", ); } /** * {@inheritDoc} */ public function acceptSchema(Schema $schema) { $this->schema = $schema; } /** * {@inheritDoc} */ public function acceptTable(Table $table) { if ($this->schema === null) { return; } if ($table->isInDefaultNamespace($this->schema->getName())) { return; } $this->schema->dropTable($table->getName()); } /** * {@inheritDoc} */ public function acceptSequence(Sequence $sequence) { if ($this->schema === null) { return; } if ($sequence->isInDefaultNamespace($this->schema->getName())) { return; } $this->schema->dropSequence($sequence->getName()); } /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { if ($this->schema === null) { return; } // The table may already be deleted in a previous // RemoveNamespacedAssets#acceptTable call. Removing Foreign keys that // point to nowhere. if (! $this->schema->hasTable($fkConstraint->getForeignTableName())) { $localTable->removeForeignKey($fkConstraint->getName()); return; } $foreignTable = $this->schema->getTable($fkConstraint->getForeignTableName()); if ($foreignTable->isInDefaultNamespace($this->schema->getName())) { return; } $localTable->removeForeignKey($fkConstraint->getName()); } } dbal/src/Schema/Visitor/DropSchemaSqlCollector.php 0000644 00000005653 15021222234 0016163 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\Deprecations\Deprecation; use SplObjectStorage; use function assert; use function strlen; /** * Gathers SQL statements that allow to completely drop the current schema. * * @deprecated Use {@link DropSchemaObjectsSQLBuilder} instead. */ class DropSchemaSqlCollector extends AbstractVisitor { private SplObjectStorage $constraints; private SplObjectStorage $sequences; private SplObjectStorage $tables; private AbstractPlatform $platform; public function __construct(AbstractPlatform $platform) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5416', 'DropSchemaSqlCollector is deprecated. Use DropSchemaObjectsSQLBuilder instead.', ); $this->platform = $platform; $this->initializeQueries(); } /** * {@inheritDoc} */ public function acceptTable(Table $table) { $this->tables->attach($table); } /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { if (strlen($fkConstraint->getName()) === 0) { throw SchemaException::namedForeignKeyRequired($localTable, $fkConstraint); } $this->constraints->attach($fkConstraint, $localTable); } /** * {@inheritDoc} */ public function acceptSequence(Sequence $sequence) { $this->sequences->attach($sequence); } /** @return void */ public function clearQueries() { $this->initializeQueries(); } /** @return string[] */ public function getQueries() { $sql = []; foreach ($this->constraints as $fkConstraint) { assert($fkConstraint instanceof ForeignKeyConstraint); $localTable = $this->constraints[$fkConstraint]; $sql[] = $this->platform->getDropForeignKeySQL( $fkConstraint->getQuotedName($this->platform), $localTable->getQuotedName($this->platform), ); } foreach ($this->sequences as $sequence) { assert($sequence instanceof Sequence); $sql[] = $this->platform->getDropSequenceSQL($sequence->getQuotedName($this->platform)); } foreach ($this->tables as $table) { assert($table instanceof Table); $sql[] = $this->platform->getDropTableSQL($table->getQuotedName($this->platform)); } return $sql; } private function initializeQueries(): void { $this->constraints = new SplObjectStorage(); $this->sequences = new SplObjectStorage(); $this->tables = new SplObjectStorage(); } } dbal/src/Schema/Visitor/AbstractVisitor.php 0000644 00000001671 15021222234 0014726 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; /** * Abstract Visitor with empty methods for easy extension. * * @deprecated */ class AbstractVisitor implements Visitor, NamespaceVisitor { public function acceptSchema(Schema $schema) { } /** * {@inheritDoc} */ public function acceptNamespace($namespaceName) { } public function acceptTable(Table $table) { } public function acceptColumn(Table $table, Column $column) { } public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { } public function acceptIndex(Table $table, Index $index) { } public function acceptSequence(Sequence $sequence) { } } dbal/src/Schema/Visitor/Visitor.php 0000644 00000002011 15021222234 0013227 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; /** * Schema Visitor used for Validation or Generation purposes. * * @deprecated */ interface Visitor { /** * @return void * * @throws SchemaException */ public function acceptSchema(Schema $schema); /** @return void */ public function acceptTable(Table $table); /** @return void */ public function acceptColumn(Table $table, Column $column); /** * @return void * * @throws SchemaException */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint); /** @return void */ public function acceptIndex(Table $table, Index $index); /** @return void */ public function acceptSequence(Sequence $sequence); } dbal/src/Schema/Visitor/CreateSchemaSqlCollector.php 0000644 00000005244 15021222234 0016456 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\Deprecations\Deprecation; use function array_merge; /** @deprecated Use {@link CreateSchemaObjectsSQLBuilder} instead. */ class CreateSchemaSqlCollector extends AbstractVisitor { /** @var string[] */ private array $createNamespaceQueries = []; /** @var string[] */ private array $createTableQueries = []; /** @var string[] */ private array $createSequenceQueries = []; /** @var string[] */ private array $createFkConstraintQueries = []; private AbstractPlatform $platform; public function __construct(AbstractPlatform $platform) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5416', 'CreateSchemaSqlCollector is deprecated. Use CreateSchemaObjectsSQLBuilder instead.', ); $this->platform = $platform; } /** * {@inheritDoc} */ public function acceptNamespace($namespaceName) { if (! $this->platform->supportsSchemas()) { return; } $this->createNamespaceQueries[] = $this->platform->getCreateSchemaSQL($namespaceName); } /** * {@inheritDoc} */ public function acceptTable(Table $table) { $this->createTableQueries = array_merge($this->createTableQueries, $this->platform->getCreateTableSQL($table)); } /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { if (! $this->platform->supportsForeignKeyConstraints()) { return; } $this->createFkConstraintQueries[] = $this->platform->getCreateForeignKeySQL($fkConstraint, $localTable); } /** * {@inheritDoc} */ public function acceptSequence(Sequence $sequence) { $this->createSequenceQueries[] = $this->platform->getCreateSequenceSQL($sequence); } /** @return void */ public function resetQueries() { $this->createNamespaceQueries = []; $this->createTableQueries = []; $this->createSequenceQueries = []; $this->createFkConstraintQueries = []; } /** * Gets all queries collected so far. * * @return string[] */ public function getQueries() { return array_merge( $this->createNamespaceQueries, $this->createSequenceQueries, $this->createTableQueries, $this->createFkConstraintQueries, ); } } dbal/src/Schema/Visitor/NamespaceVisitor.php 0000644 00000000556 15021222234 0015060 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; /** * Visitor that can visit schema namespaces. * * @deprecated */ interface NamespaceVisitor { /** * Accepts a schema namespace name. * * @param string $namespaceName The schema namespace name to accept. * * @return void */ public function acceptNamespace($namespaceName); } dbal/src/Schema/Visitor/Graphviz.php 0000644 00000010645 15021222234 0013376 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Table; use function current; use function file_put_contents; use function in_array; use function strtolower; /** * Create a Graphviz output of a Schema. * * @deprecated */ class Graphviz extends AbstractVisitor { private string $output = ''; /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { $this->output .= $this->createNodeRelation( $fkConstraint->getLocalTableName() . ':col' . current($fkConstraint->getLocalColumns()) . ':se', $fkConstraint->getForeignTableName() . ':col' . current($fkConstraint->getForeignColumns()) . ':se', [ 'dir' => 'back', 'arrowtail' => 'dot', 'arrowhead' => 'normal', ], ); } /** * {@inheritDoc} */ public function acceptSchema(Schema $schema) { $this->output = 'digraph "' . $schema->getName() . '" {' . "\n"; $this->output .= 'splines = true;' . "\n"; $this->output .= 'overlap = false;' . "\n"; $this->output .= 'outputorder=edgesfirst;' . "\n"; $this->output .= 'mindist = 0.6;' . "\n"; $this->output .= 'sep = .2;' . "\n"; } /** * {@inheritDoc} */ public function acceptTable(Table $table) { $this->output .= $this->createNode( $table->getName(), [ 'label' => $this->createTableLabel($table), 'shape' => 'plaintext', ], ); } private function createTableLabel(Table $table): string { // Start the table $label = '<<TABLE CELLSPACING="0" BORDER="1" ALIGN="LEFT">'; // The title $label .= '<TR><TD BORDER="1" COLSPAN="3" ALIGN="CENTER" BGCOLOR="#fcaf3e">' . '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="12">' . $table->getName() . '</FONT></TD></TR>'; // The attributes block foreach ($table->getColumns() as $column) { $columnLabel = $column->getName(); $label .= '<TR>' . '<TD BORDER="0" ALIGN="LEFT" BGCOLOR="#eeeeec">' . '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="12">' . $columnLabel . '</FONT>' . '</TD>' . '<TD BORDER="0" ALIGN="LEFT" BGCOLOR="#eeeeec">' . '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="10">' . strtolower($column->getType()->getName()) . '</FONT>' . '</TD>' . '<TD BORDER="0" ALIGN="RIGHT" BGCOLOR="#eeeeec" PORT="col' . $column->getName() . '">'; $primaryKey = $table->getPrimaryKey(); if ($primaryKey !== null && in_array($column->getName(), $primaryKey->getColumns(), true)) { $label .= "\xe2\x9c\xb7"; } $label .= '</TD></TR>'; } // End the table $label .= '</TABLE>>'; return $label; } /** * @param string $name * @param string[] $options */ private function createNode($name, $options): string { $node = $name . ' ['; foreach ($options as $key => $value) { $node .= $key . '=' . $value . ' '; } $node .= "]\n"; return $node; } /** * @param string $node1 * @param string $node2 * @param string[] $options */ private function createNodeRelation($node1, $node2, $options): string { $relation = $node1 . ' -> ' . $node2 . ' ['; foreach ($options as $key => $value) { $relation .= $key . '=' . $value . ' '; } $relation .= "]\n"; return $relation; } /** * Get Graphviz Output * * @return string */ public function getOutput() { return $this->output . '}'; } /** * Writes dot language output to a file. This should usually be a *.dot file. * * You have to convert the output into a viewable format. For example use "neato" on linux systems * and execute: * * neato -Tpng -o er.png er.dot * * @param string $filename * * @return void */ public function write($filename) { file_put_contents($filename, $this->getOutput()); } } dbal/src/Schema/DefaultSchemaManagerFactory.php 0000644 00000001102 15021222234 0015461 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; /** * A schema manager factory that returns the default schema manager for the given platform. */ final class DefaultSchemaManagerFactory implements SchemaManagerFactory { /** @throws Exception If the platform does not support creating schema managers yet. */ public function createSchemaManager(Connection $connection): AbstractSchemaManager { return $connection->getDatabasePlatform()->createSchemaManager($connection); } } dbal/src/Schema/SqliteSchemaManager.php 0000644 00000055005 15021222234 0014021 0 ustar 00 <?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} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ 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; } } dbal/src/Schema/Comparator.php 0000644 00000057062 15021222234 0012260 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use BadMethodCallException; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types; use Doctrine\Deprecations\Deprecation; use function array_intersect_key; use function array_key_exists; use function array_keys; use function array_map; use function array_merge; use function array_unique; use function assert; use function count; use function get_class; use function sprintf; use function strtolower; /** * Compares two Schemas and return an instance of SchemaDiff. * * @method SchemaDiff compareSchemas(Schema $fromSchema, Schema $toSchema) */ class Comparator { private ?AbstractPlatform $platform; /** @internal The comparator can be only instantiated by a schema manager. */ public function __construct(?AbstractPlatform $platform = null) { if ($platform === null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4746', 'Not passing a $platform to %s is deprecated.' . ' Use AbstractSchemaManager::createComparator() to instantiate the comparator.', __METHOD__, ); } $this->platform = $platform; } /** @param list<mixed> $args */ public function __call(string $method, array $args): SchemaDiff { if ($method !== 'compareSchemas') { throw new BadMethodCallException(sprintf('Unknown method "%s"', $method)); } return $this->doCompareSchemas(...$args); } /** @param list<mixed> $args */ public static function __callStatic(string $method, array $args): SchemaDiff { if ($method !== 'compareSchemas') { throw new BadMethodCallException(sprintf('Unknown method "%s"', $method)); } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4707', 'Calling %s::%s() statically is deprecated.', self::class, $method, ); $comparator = new self(); return $comparator->doCompareSchemas(...$args); } /** * Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema. * * This method should be called non-statically since it will be declared as non-static in the next major release. * * @return SchemaDiff * * @throws SchemaException */ private function doCompareSchemas( Schema $fromSchema, Schema $toSchema ) { $createdSchemas = []; $droppedSchemas = []; $createdTables = []; $alteredTables = []; $droppedTables = []; $createdSequences = []; $alteredSequences = []; $droppedSequences = []; $orphanedForeignKeys = []; $foreignKeysToTable = []; foreach ($toSchema->getNamespaces() as $namespace) { if ($fromSchema->hasNamespace($namespace)) { continue; } $createdSchemas[$namespace] = $namespace; } foreach ($fromSchema->getNamespaces() as $namespace) { if ($toSchema->hasNamespace($namespace)) { continue; } $droppedSchemas[$namespace] = $namespace; } foreach ($toSchema->getTables() as $table) { $tableName = $table->getShortestName($toSchema->getName()); if (! $fromSchema->hasTable($tableName)) { $createdTables[$tableName] = $toSchema->getTable($tableName); } else { $tableDifferences = $this->diffTable( $fromSchema->getTable($tableName), $toSchema->getTable($tableName), ); if ($tableDifferences !== false) { $alteredTables[$tableName] = $tableDifferences; } } } /* Check if there are tables removed */ foreach ($fromSchema->getTables() as $table) { $tableName = $table->getShortestName($fromSchema->getName()); $table = $fromSchema->getTable($tableName); if (! $toSchema->hasTable($tableName)) { $droppedTables[$tableName] = $table; } // also remember all foreign keys that point to a specific table foreach ($table->getForeignKeys() as $foreignKey) { $foreignTable = strtolower($foreignKey->getForeignTableName()); if (! isset($foreignKeysToTable[$foreignTable])) { $foreignKeysToTable[$foreignTable] = []; } $foreignKeysToTable[$foreignTable][] = $foreignKey; } } foreach ($droppedTables as $tableName => $table) { if (! isset($foreignKeysToTable[$tableName])) { continue; } foreach ($foreignKeysToTable[$tableName] as $foreignKey) { if (isset($droppedTables[strtolower($foreignKey->getLocalTableName())])) { continue; } $orphanedForeignKeys[] = $foreignKey; } // deleting duplicated foreign keys present on both on the orphanedForeignKey // and the removedForeignKeys from changedTables foreach ($foreignKeysToTable[$tableName] as $foreignKey) { // strtolower the table name to make if compatible with getShortestName $localTableName = strtolower($foreignKey->getLocalTableName()); if (! isset($alteredTables[$localTableName])) { continue; } foreach ($alteredTables[$localTableName]->getDroppedForeignKeys() as $droppedForeignKey) { assert($droppedForeignKey instanceof ForeignKeyConstraint); // We check if the key is from the removed table if not we skip. if ($tableName !== strtolower($droppedForeignKey->getForeignTableName())) { continue; } $alteredTables[$localTableName]->unsetDroppedForeignKey($droppedForeignKey); } } } foreach ($toSchema->getSequences() as $sequence) { $sequenceName = $sequence->getShortestName($toSchema->getName()); if (! $fromSchema->hasSequence($sequenceName)) { if (! $this->isAutoIncrementSequenceInSchema($fromSchema, $sequence)) { $createdSequences[] = $sequence; } } else { if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) { $alteredSequences[] = $toSchema->getSequence($sequenceName); } } } foreach ($fromSchema->getSequences() as $sequence) { if ($this->isAutoIncrementSequenceInSchema($toSchema, $sequence)) { continue; } $sequenceName = $sequence->getShortestName($fromSchema->getName()); if ($toSchema->hasSequence($sequenceName)) { continue; } $droppedSequences[] = $sequence; } $diff = new SchemaDiff( $createdTables, $alteredTables, $droppedTables, $fromSchema, $createdSchemas, $droppedSchemas, $createdSequences, $alteredSequences, $droppedSequences, ); $diff->orphanedForeignKeys = $orphanedForeignKeys; return $diff; } /** * @deprecated Use non-static call to {@see compareSchemas()} instead. * * @return SchemaDiff * * @throws SchemaException */ public function compare(Schema $fromSchema, Schema $toSchema) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4707', 'Method compare() is deprecated. Use a non-static call to compareSchemas() instead.', ); return $this->compareSchemas($fromSchema, $toSchema); } /** * @param Schema $schema * @param Sequence $sequence */ private function isAutoIncrementSequenceInSchema($schema, $sequence): bool { foreach ($schema->getTables() as $table) { if ($sequence->isAutoIncrementsFor($table)) { return true; } } return false; } /** @return bool */ public function diffSequence(Sequence $sequence1, Sequence $sequence2) { if ($sequence1->getAllocationSize() !== $sequence2->getAllocationSize()) { return true; } return $sequence1->getInitialValue() !== $sequence2->getInitialValue(); } /** * Returns the difference between the tables $fromTable and $toTable. * * If there are no differences this method returns the boolean false. * * @deprecated Use {@see compareTables()} and, optionally, {@see TableDiff::isEmpty()} instead. * * @return TableDiff|false * * @throws Exception */ public function diffTable(Table $fromTable, Table $toTable) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5770', '%s is deprecated. Use compareTables() instead.', __METHOD__, ); $diff = $this->compareTables($fromTable, $toTable); if ($diff->isEmpty()) { return false; } return $diff; } /** * Compares the tables and returns the difference between them. * * @throws Exception */ public function compareTables(Table $fromTable, Table $toTable): TableDiff { $addedColumns = []; $modifiedColumns = []; $droppedColumns = []; $addedIndexes = []; $modifiedIndexes = []; $droppedIndexes = []; $addedForeignKeys = []; $modifiedForeignKeys = []; $droppedForeignKeys = []; $fromTableColumns = $fromTable->getColumns(); $toTableColumns = $toTable->getColumns(); /* See if all the columns in "from" table exist in "to" table */ foreach ($toTableColumns as $columnName => $column) { if ($fromTable->hasColumn($columnName)) { continue; } $addedColumns[$columnName] = $column; } /* See if there are any removed columns in "to" table */ foreach ($fromTableColumns as $columnName => $column) { // See if column is removed in "to" table. if (! $toTable->hasColumn($columnName)) { $droppedColumns[$columnName] = $column; continue; } $toColumn = $toTable->getColumn($columnName); // See if column has changed properties in "to" table. $changedProperties = $this->diffColumn($column, $toColumn); if ($this->platform !== null) { if ($this->columnsEqual($column, $toColumn)) { continue; } } elseif (count($changedProperties) === 0) { continue; } $modifiedColumns[$column->getName()] = new ColumnDiff( $column->getName(), $toColumn, $changedProperties, $column, ); } $renamedColumns = $this->detectRenamedColumns($addedColumns, $droppedColumns); $fromTableIndexes = $fromTable->getIndexes(); $toTableIndexes = $toTable->getIndexes(); /* See if all the indexes in "from" table exist in "to" table */ foreach ($toTableIndexes as $indexName => $index) { if (($index->isPrimary() && $fromTable->getPrimaryKey() !== null) || $fromTable->hasIndex($indexName)) { continue; } $addedIndexes[$indexName] = $index; } /* See if there are any removed indexes in "to" table */ foreach ($fromTableIndexes as $indexName => $index) { // See if index is removed in "to" table. if ( ($index->isPrimary() && $toTable->getPrimaryKey() === null) || ! $index->isPrimary() && ! $toTable->hasIndex($indexName) ) { $droppedIndexes[$indexName] = $index; continue; } // See if index has changed in "to" table. $toTableIndex = $index->isPrimary() ? $toTable->getPrimaryKey() : $toTable->getIndex($indexName); assert($toTableIndex instanceof Index); if (! $this->diffIndex($index, $toTableIndex)) { continue; } $modifiedIndexes[$indexName] = $toTableIndex; } $renamedIndexes = $this->detectRenamedIndexes($addedIndexes, $droppedIndexes); $fromForeignKeys = $fromTable->getForeignKeys(); $toForeignKeys = $toTable->getForeignKeys(); foreach ($fromForeignKeys as $fromKey => $fromConstraint) { foreach ($toForeignKeys as $toKey => $toConstraint) { if ($this->diffForeignKey($fromConstraint, $toConstraint) === false) { unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]); } else { if (strtolower($fromConstraint->getName()) === strtolower($toConstraint->getName())) { $modifiedForeignKeys[] = $toConstraint; unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]); } } } } foreach ($fromForeignKeys as $fromConstraint) { $droppedForeignKeys[] = $fromConstraint; } foreach ($toForeignKeys as $toConstraint) { $addedForeignKeys[] = $toConstraint; } return new TableDiff( $toTable->getName(), $addedColumns, $modifiedColumns, $droppedColumns, $addedIndexes, $modifiedIndexes, $droppedIndexes, $fromTable, $addedForeignKeys, $modifiedForeignKeys, $droppedForeignKeys, $renamedColumns, $renamedIndexes, ); } /** * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop * however ambiguities between different possibilities should not lead to renaming at all. * * @param array<string,Column> $addedColumns * @param array<string,Column> $removedColumns * * @return array<string,Column> * * @throws Exception */ private function detectRenamedColumns(array &$addedColumns, array &$removedColumns): array { $candidatesByName = []; foreach ($addedColumns as $addedColumnName => $addedColumn) { foreach ($removedColumns as $removedColumn) { if (! $this->columnsEqual($addedColumn, $removedColumn)) { continue; } $candidatesByName[$addedColumn->getName()][] = [$removedColumn, $addedColumn, $addedColumnName]; } } $renamedColumns = []; foreach ($candidatesByName as $candidates) { if (count($candidates) !== 1) { continue; } [$removedColumn, $addedColumn] = $candidates[0]; $removedColumnName = $removedColumn->getName(); $addedColumnName = strtolower($addedColumn->getName()); if (isset($renamedColumns[$removedColumnName])) { continue; } $renamedColumns[$removedColumnName] = $addedColumn; unset( $addedColumns[$addedColumnName], $removedColumns[strtolower($removedColumnName)], ); } return $renamedColumns; } /** * Try to find indexes that only changed their name, rename operations maybe cheaper than add/drop * however ambiguities between different possibilities should not lead to renaming at all. * * @param array<string,Index> $addedIndexes * @param array<string,Index> $removedIndexes * * @return array<string,Index> */ private function detectRenamedIndexes(array &$addedIndexes, array &$removedIndexes): array { $candidatesByName = []; // Gather possible rename candidates by comparing each added and removed index based on semantics. foreach ($addedIndexes as $addedIndexName => $addedIndex) { foreach ($removedIndexes as $removedIndex) { if ($this->diffIndex($addedIndex, $removedIndex)) { continue; } $candidatesByName[$addedIndex->getName()][] = [$removedIndex, $addedIndex, $addedIndexName]; } } $renamedIndexes = []; foreach ($candidatesByName as $candidates) { // If the current rename candidate contains exactly one semantically equal index, // we can safely rename it. // Otherwise, it is unclear if a rename action is really intended, // therefore we let those ambiguous indexes be added/dropped. if (count($candidates) !== 1) { continue; } [$removedIndex, $addedIndex] = $candidates[0]; $removedIndexName = strtolower($removedIndex->getName()); $addedIndexName = strtolower($addedIndex->getName()); if (isset($renamedIndexes[$removedIndexName])) { continue; } $renamedIndexes[$removedIndexName] = $addedIndex; unset( $addedIndexes[$addedIndexName], $removedIndexes[$removedIndexName], ); } return $renamedIndexes; } /** * @internal The method should be only used from within the {@see Comparator} class hierarchy. * * @return bool */ public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2) { if ( array_map('strtolower', $key1->getUnquotedLocalColumns()) !== array_map('strtolower', $key2->getUnquotedLocalColumns()) ) { return true; } if ( array_map('strtolower', $key1->getUnquotedForeignColumns()) !== array_map('strtolower', $key2->getUnquotedForeignColumns()) ) { return true; } if ($key1->getUnqualifiedForeignTableName() !== $key2->getUnqualifiedForeignTableName()) { return true; } if ($key1->onUpdate() !== $key2->onUpdate()) { return true; } return $key1->onDelete() !== $key2->onDelete(); } /** * Compares the definitions of the given columns * * @internal The method should be only used from within the {@see Comparator} class hierarchy. * * @throws Exception */ public function columnsEqual(Column $column1, Column $column2): bool { if ($this->platform === null) { return $this->diffColumn($column1, $column2) === []; } return $this->platform->columnsEqual($column1, $column2); } /** * Returns the difference between the columns * * If there are differences this method returns the changed properties as a * string array, otherwise an empty array gets returned. * * @deprecated Use {@see columnsEqual()} instead. * * @return string[] */ public function diffColumn(Column $column1, Column $column2) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5650', '%s is deprecated. Use diffTable() instead.', __METHOD__, ); $properties1 = $column1->toArray(); $properties2 = $column2->toArray(); $changedProperties = []; if (get_class($properties1['type']) !== get_class($properties2['type'])) { $changedProperties[] = 'type'; } foreach (['notnull', 'unsigned', 'autoincrement'] as $property) { if ($properties1[$property] === $properties2[$property]) { continue; } $changedProperties[] = $property; } // Null values need to be checked additionally as they tell whether to create or drop a default value. // null != 0, null != false, null != '' etc. This affects platform's table alteration SQL generation. if ( ($properties1['default'] === null) !== ($properties2['default'] === null) || $properties1['default'] != $properties2['default'] ) { $changedProperties[] = 'default'; } if ( ($properties1['type'] instanceof Types\StringType && ! $properties1['type'] instanceof Types\GuidType) || $properties1['type'] instanceof Types\BinaryType ) { // check if value of length is set at all, default value assumed otherwise. $length1 = $properties1['length'] ?? 255; $length2 = $properties2['length'] ?? 255; if ($length1 !== $length2) { $changedProperties[] = 'length'; } if ($properties1['fixed'] !== $properties2['fixed']) { $changedProperties[] = 'fixed'; } } elseif ($properties1['type'] instanceof Types\DecimalType) { if (($properties1['precision'] ?? 10) !== ($properties2['precision'] ?? 10)) { $changedProperties[] = 'precision'; } if ($properties1['scale'] !== $properties2['scale']) { $changedProperties[] = 'scale'; } } // A null value and an empty string are actually equal for a comment so they should not trigger a change. if ( $properties1['comment'] !== $properties2['comment'] && ! ($properties1['comment'] === null && $properties2['comment'] === '') && ! ($properties2['comment'] === null && $properties1['comment'] === '') ) { $changedProperties[] = 'comment'; } $customOptions1 = $column1->getCustomSchemaOptions(); $customOptions2 = $column2->getCustomSchemaOptions(); foreach (array_merge(array_keys($customOptions1), array_keys($customOptions2)) as $key) { if (! array_key_exists($key, $properties1) || ! array_key_exists($key, $properties2)) { $changedProperties[] = $key; } elseif ($properties1[$key] !== $properties2[$key]) { $changedProperties[] = $key; } } $platformOptions1 = $column1->getPlatformOptions(); $platformOptions2 = $column2->getPlatformOptions(); foreach (array_keys(array_intersect_key($platformOptions1, $platformOptions2)) as $key) { if ($properties1[$key] === $properties2[$key]) { continue; } $changedProperties[] = $key; } return array_unique($changedProperties); } /** * Finds the difference between the indexes $index1 and $index2. * * Compares $index1 with $index2 and returns true if there are any * differences or false in case there are no differences. * * @internal The method should be only used from within the {@see Comparator} class hierarchy. * * @return bool */ public function diffIndex(Index $index1, Index $index2) { return ! ($index1->isFulfilledBy($index2) && $index2->isFulfilledBy($index1)); } } dbal/src/Schema/LegacySchemaManagerFactory.php 0000644 00000000707 15021222234 0015313 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Connection; /** @internal Will be removed in 4.0. */ final class LegacySchemaManagerFactory implements SchemaManagerFactory { public function createSchemaManager(Connection $connection): AbstractSchemaManager { return $connection->getDriver()->getSchemaManager( $connection, $connection->getDatabasePlatform(), ); } } dbal/src/Schema/Table.php 0000644 00000067434 15021222234 0011204 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\Exception\InvalidTableName; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_filter; use function array_keys; use function array_merge; use function in_array; use function preg_match; use function strlen; use function strtolower; use const ARRAY_FILTER_USE_KEY; /** * Object Representation of a table. */ class Table extends AbstractAsset { /** @var Column[] */ protected $_columns = []; /** @var Index[] */ protected $_indexes = []; /** @var string|null */ protected $_primaryKeyName; /** @var UniqueConstraint[] */ protected $uniqueConstraints = []; /** @var ForeignKeyConstraint[] */ protected $_fkConstraints = []; /** @var mixed[] */ protected $_options = [ 'create_options' => [], ]; /** @var SchemaConfig|null */ protected $_schemaConfig; /** @var Index[] */ private array $implicitIndexes = []; /** * @param Column[] $columns * @param Index[] $indexes * @param UniqueConstraint[] $uniqueConstraints * @param ForeignKeyConstraint[] $fkConstraints * @param mixed[] $options * * @throws SchemaException * @throws Exception */ public function __construct( string $name, array $columns = [], array $indexes = [], array $uniqueConstraints = [], array $fkConstraints = [], array $options = [] ) { if ($name === '') { throw InvalidTableName::new($name); } $this->_setName($name); foreach ($columns as $column) { $this->_addColumn($column); } foreach ($indexes as $idx) { $this->_addIndex($idx); } foreach ($uniqueConstraints as $uniqueConstraint) { $this->_addUniqueConstraint($uniqueConstraint); } foreach ($fkConstraints as $constraint) { $this->_addForeignKeyConstraint($constraint); } $this->_options = array_merge($this->_options, $options); } /** @return void */ public function setSchemaConfig(SchemaConfig $schemaConfig) { $this->_schemaConfig = $schemaConfig; } /** @return int */ protected function _getMaxIdentifierLength() { if ($this->_schemaConfig instanceof SchemaConfig) { return $this->_schemaConfig->getMaxIdentifierLength(); } return 63; } /** * Sets the Primary Key. * * @param string[] $columnNames * @param string|false $indexName * * @return self * * @throws SchemaException */ public function setPrimaryKey(array $columnNames, $indexName = false) { if ($indexName === false) { $indexName = 'primary'; } $this->_addIndex($this->_createIndex($columnNames, $indexName, true, true)); foreach ($columnNames as $columnName) { $column = $this->getColumn($columnName); $column->setNotnull(true); } return $this; } /** * @param string[] $columnNames * @param string[] $flags * @param mixed[] $options * * @return self * * @throws SchemaException */ public function addIndex(array $columnNames, ?string $indexName = null, array $flags = [], array $options = []) { $indexName ??= $this->_generateIdentifierName( array_merge([$this->getName()], $columnNames), 'idx', $this->_getMaxIdentifierLength(), ); return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options)); } /** * @param string[] $columnNames * @param string[] $flags * @param mixed[] $options * * @return self */ public function addUniqueConstraint( array $columnNames, ?string $indexName = null, array $flags = [], array $options = [] ): Table { $indexName ??= $this->_generateIdentifierName( array_merge([$this->getName()], $columnNames), 'uniq', $this->_getMaxIdentifierLength(), ); return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $flags, $options)); } /** * Drops the primary key from this table. * * @return void * * @throws SchemaException */ public function dropPrimaryKey() { if ($this->_primaryKeyName === null) { return; } $this->dropIndex($this->_primaryKeyName); $this->_primaryKeyName = null; } /** * Drops an index from this table. * * @param string $name The index name. * * @return void * * @throws SchemaException If the index does not exist. */ public function dropIndex($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasIndex($name)) { throw SchemaException::indexDoesNotExist($name, $this->_name); } unset($this->_indexes[$name]); } /** * @param string[] $columnNames * @param string|null $indexName * @param mixed[] $options * * @return self * * @throws SchemaException */ public function addUniqueIndex(array $columnNames, $indexName = null, array $options = []) { $indexName ??= $this->_generateIdentifierName( array_merge([$this->getName()], $columnNames), 'uniq', $this->_getMaxIdentifierLength(), ); return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options)); } /** * Renames an index. * * @param string $oldName The name of the index to rename from. * @param string|null $newName The name of the index to rename to. * If null is given, the index name will be auto-generated. * * @return self This table instance. * * @throws SchemaException If no index exists for the given current name * or if an index with the given new name already exists on this table. */ public function renameIndex($oldName, $newName = null) { $oldName = $this->normalizeIdentifier($oldName); $normalizedNewName = $this->normalizeIdentifier($newName); if ($oldName === $normalizedNewName) { return $this; } if (! $this->hasIndex($oldName)) { throw SchemaException::indexDoesNotExist($oldName, $this->_name); } if ($this->hasIndex($normalizedNewName)) { throw SchemaException::indexAlreadyExists($normalizedNewName, $this->_name); } $oldIndex = $this->_indexes[$oldName]; if ($oldIndex->isPrimary()) { $this->dropPrimaryKey(); return $this->setPrimaryKey($oldIndex->getColumns(), $newName ?? false); } unset($this->_indexes[$oldName]); if ($oldIndex->isUnique()) { return $this->addUniqueIndex($oldIndex->getColumns(), $newName, $oldIndex->getOptions()); } return $this->addIndex($oldIndex->getColumns(), $newName, $oldIndex->getFlags(), $oldIndex->getOptions()); } /** * Checks if an index begins in the order of the given columns. * * @param string[] $columnNames * * @return bool */ public function columnsAreIndexed(array $columnNames) { foreach ($this->getIndexes() as $index) { if ($index->spansColumns($columnNames)) { return true; } } return false; } /** * @param string[] $columnNames * @param string $indexName * @param bool $isUnique * @param bool $isPrimary * @param string[] $flags * @param mixed[] $options * * @throws SchemaException */ private function _createIndex( array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [] ): Index { if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) { throw SchemaException::indexNameInvalid($indexName); } foreach ($columnNames as $columnName) { if (! $this->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $this->_name); } } return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options); } /** * @param string $name * @param string $typeName * @param mixed[] $options * * @return Column * * @throws SchemaException */ public function addColumn($name, $typeName, array $options = []) { $column = new Column($name, Type::getType($typeName), $options); $this->_addColumn($column); return $column; } /** * Change Column Details. * * @deprecated Use {@link modifyColumn()} instead. * * @param string $name * @param mixed[] $options * * @return self * * @throws SchemaException */ public function changeColumn($name, array $options) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5747', '%s is deprecated. Use modifyColumn() instead.', __METHOD__, ); return $this->modifyColumn($name, $options); } /** * @param string $name * @param mixed[] $options * * @return self * * @throws SchemaException */ public function modifyColumn($name, array $options) { $column = $this->getColumn($name); $column->setOptions($options); return $this; } /** * Drops a Column from the Table. * * @param string $name * * @return self */ public function dropColumn($name) { $name = $this->normalizeIdentifier($name); unset($this->_columns[$name]); return $this; } /** * Adds a foreign key constraint. * * Name is inferred from the local columns. * * @param Table|string $foreignTable Table schema instance or table name * @param string[] $localColumnNames * @param string[] $foreignColumnNames * @param mixed[] $options * @param string|null $name * * @return self * * @throws SchemaException */ public function addForeignKeyConstraint( $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [], $name = null ) { $name ??= $this->_generateIdentifierName( array_merge([$this->getName()], $localColumnNames), 'fk', $this->_getMaxIdentifierLength(), ); if ($foreignTable instanceof Table) { foreach ($foreignColumnNames as $columnName) { if (! $foreignTable->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName()); } } } foreach ($localColumnNames as $columnName) { if (! $this->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $this->_name); } } $constraint = new ForeignKeyConstraint( $localColumnNames, $foreignTable, $foreignColumnNames, $name, $options, ); return $this->_addForeignKeyConstraint($constraint); } /** * @param string $name * @param mixed $value * * @return self */ public function addOption($name, $value) { $this->_options[$name] = $value; return $this; } /** * @return void * * @throws SchemaException */ protected function _addColumn(Column $column) { $columnName = $column->getName(); $columnName = $this->normalizeIdentifier($columnName); if (isset($this->_columns[$columnName])) { throw SchemaException::columnAlreadyExists($this->getName(), $columnName); } $this->_columns[$columnName] = $column; } /** * Adds an index to the table. * * @return self * * @throws SchemaException */ protected function _addIndex(Index $indexCandidate) { $indexName = $indexCandidate->getName(); $indexName = $this->normalizeIdentifier($indexName); $replacedImplicitIndexes = []; foreach ($this->implicitIndexes as $name => $implicitIndex) { if (! $implicitIndex->isFulfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) { continue; } $replacedImplicitIndexes[] = $name; } if ( (isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) || ($this->_primaryKeyName !== null && $indexCandidate->isPrimary()) ) { throw SchemaException::indexAlreadyExists($indexName, $this->_name); } foreach ($replacedImplicitIndexes as $name) { unset($this->_indexes[$name], $this->implicitIndexes[$name]); } if ($indexCandidate->isPrimary()) { $this->_primaryKeyName = $indexName; } $this->_indexes[$indexName] = $indexCandidate; return $this; } /** @return self */ protected function _addUniqueConstraint(UniqueConstraint $constraint): Table { $mergedNames = array_merge([$this->getName()], $constraint->getColumns()); $name = strlen($constraint->getName()) > 0 ? $constraint->getName() : $this->_generateIdentifierName($mergedNames, 'fk', $this->_getMaxIdentifierLength()); $name = $this->normalizeIdentifier($name); $this->uniqueConstraints[$name] = $constraint; // If there is already an index that fulfills this requirements drop the request. In the case of __construct // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates. // This creates computation overhead in this case, however no duplicate indexes are ever added (column based). $indexName = $this->_generateIdentifierName($mergedNames, 'idx', $this->_getMaxIdentifierLength()); $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, true, false); foreach ($this->_indexes as $existingIndex) { if ($indexCandidate->isFulfilledBy($existingIndex)) { return $this; } } $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate; return $this; } /** @return self */ protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint) { $constraint->setLocalTable($this); if (strlen($constraint->getName()) > 0) { $name = $constraint->getName(); } else { $name = $this->_generateIdentifierName( array_merge([$this->getName()], $constraint->getLocalColumns()), 'fk', $this->_getMaxIdentifierLength(), ); } $name = $this->normalizeIdentifier($name); $this->_fkConstraints[$name] = $constraint; /* Add an implicit index (defined by the DBAL) on the foreign key columns. If there is already a user-defined index that fulfills these requirements drop the request. In the case of __construct() calling this method during hydration from schema-details, all the explicitly added indexes lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns). */ $indexName = $this->_generateIdentifierName( array_merge([$this->getName()], $constraint->getColumns()), 'idx', $this->_getMaxIdentifierLength(), ); $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false); foreach ($this->_indexes as $existingIndex) { if ($indexCandidate->isFulfilledBy($existingIndex)) { return $this; } } $this->_addIndex($indexCandidate); $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate; return $this; } /** * Returns whether this table has a foreign key constraint with the given name. * * @param string $name * * @return bool */ public function hasForeignKey($name) { $name = $this->normalizeIdentifier($name); return isset($this->_fkConstraints[$name]); } /** * Returns the foreign key constraint with the given name. * * @param string $name The constraint name. * * @return ForeignKeyConstraint * * @throws SchemaException If the foreign key does not exist. */ public function getForeignKey($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasForeignKey($name)) { throw SchemaException::foreignKeyDoesNotExist($name, $this->_name); } return $this->_fkConstraints[$name]; } /** * Removes the foreign key constraint with the given name. * * @param string $name The constraint name. * * @return void * * @throws SchemaException */ public function removeForeignKey($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasForeignKey($name)) { throw SchemaException::foreignKeyDoesNotExist($name, $this->_name); } unset($this->_fkConstraints[$name]); } /** * Returns whether this table has a unique constraint with the given name. */ public function hasUniqueConstraint(string $name): bool { $name = $this->normalizeIdentifier($name); return isset($this->uniqueConstraints[$name]); } /** * Returns the unique constraint with the given name. * * @throws SchemaException If the unique constraint does not exist. */ public function getUniqueConstraint(string $name): UniqueConstraint { $name = $this->normalizeIdentifier($name); if (! $this->hasUniqueConstraint($name)) { throw SchemaException::uniqueConstraintDoesNotExist($name, $this->_name); } return $this->uniqueConstraints[$name]; } /** * Removes the unique constraint with the given name. * * @throws SchemaException If the unique constraint does not exist. */ public function removeUniqueConstraint(string $name): void { $name = $this->normalizeIdentifier($name); if (! $this->hasUniqueConstraint($name)) { throw SchemaException::uniqueConstraintDoesNotExist($name, $this->_name); } unset($this->uniqueConstraints[$name]); } /** * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest) * * @return Column[] */ public function getColumns() { $primaryKeyColumns = $this->getPrimaryKey() !== null ? $this->getPrimaryKeyColumns() : []; $foreignKeyColumns = $this->getForeignKeyColumns(); $remainderColumns = $this->filterColumns( array_merge(array_keys($primaryKeyColumns), array_keys($foreignKeyColumns)), true, ); return array_merge($primaryKeyColumns, $foreignKeyColumns, $remainderColumns); } /** * Returns the foreign key columns * * @deprecated Use {@see getForeignKey()} and {@see ForeignKeyConstraint::getLocalColumns()} instead. * * @return Column[] */ public function getForeignKeyColumns() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5731', '%s is deprecated. Use getForeignKey() and ForeignKeyConstraint::getLocalColumns() instead.', __METHOD__, ); $foreignKeyColumns = []; foreach ($this->getForeignKeys() as $foreignKey) { $foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getLocalColumns()); } return $this->filterColumns($foreignKeyColumns); } /** * Returns only columns that have specified names * * @param string[] $columnNames * * @return Column[] */ private function filterColumns(array $columnNames, bool $reverse = false): array { return array_filter($this->_columns, static function (string $columnName) use ($columnNames, $reverse): bool { return in_array($columnName, $columnNames, true) !== $reverse; }, ARRAY_FILTER_USE_KEY); } /** * Returns whether this table has a Column with the given name. * * @param string $name The column name. * * @return bool */ public function hasColumn($name) { $name = $this->normalizeIdentifier($name); return isset($this->_columns[$name]); } /** * Returns the Column with the given name. * * @param string $name The column name. * * @return Column * * @throws SchemaException If the column does not exist. */ public function getColumn($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasColumn($name)) { throw SchemaException::columnDoesNotExist($name, $this->_name); } return $this->_columns[$name]; } /** * Returns the primary key. * * @return Index|null The primary key, or null if this Table has no primary key. */ public function getPrimaryKey() { if ($this->_primaryKeyName !== null) { return $this->getIndex($this->_primaryKeyName); } return null; } /** * Returns the primary key columns. * * @deprecated Use {@see getPrimaryKey()} and {@see Index::getColumns()} instead. * * @return Column[] * * @throws Exception */ public function getPrimaryKeyColumns() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5731', '%s is deprecated. Use getPrimaryKey() and Index::getColumns() instead.', __METHOD__, ); $primaryKey = $this->getPrimaryKey(); if ($primaryKey === null) { throw new Exception('Table ' . $this->getName() . ' has no primary key.'); } return $this->filterColumns($primaryKey->getColumns()); } /** * Returns whether this table has a primary key. * * @deprecated Use {@see getPrimaryKey()} instead. * * @return bool */ public function hasPrimaryKey() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5731', '%s is deprecated. Use getPrimaryKey() instead.', __METHOD__, ); return $this->_primaryKeyName !== null && $this->hasIndex($this->_primaryKeyName); } /** * Returns whether this table has an Index with the given name. * * @param string $name The index name. * * @return bool */ public function hasIndex($name) { $name = $this->normalizeIdentifier($name); return isset($this->_indexes[$name]); } /** * Returns the Index with the given name. * * @param string $name The index name. * * @return Index * * @throws SchemaException If the index does not exist. */ public function getIndex($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasIndex($name)) { throw SchemaException::indexDoesNotExist($name, $this->_name); } return $this->_indexes[$name]; } /** @return Index[] */ public function getIndexes() { return $this->_indexes; } /** * Returns the unique constraints. * * @return UniqueConstraint[] */ public function getUniqueConstraints(): array { return $this->uniqueConstraints; } /** * Returns the foreign key constraints. * * @return ForeignKeyConstraint[] */ public function getForeignKeys() { return $this->_fkConstraints; } /** * @param string $name * * @return bool */ public function hasOption($name) { return isset($this->_options[$name]); } /** * @param string $name * * @return mixed */ public function getOption($name) { return $this->_options[$name]; } /** @return mixed[] */ public function getOptions() { return $this->_options; } /** * @deprecated * * @return void * * @throws SchemaException */ public function visit(Visitor $visitor) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5435', 'Table::visit() is deprecated.', ); $visitor->acceptTable($this); foreach ($this->getColumns() as $column) { $visitor->acceptColumn($this, $column); } foreach ($this->getIndexes() as $index) { $visitor->acceptIndex($this, $index); } foreach ($this->getForeignKeys() as $constraint) { $visitor->acceptForeignKey($this, $constraint); } } /** * Clone of a Table triggers a deep clone of all affected assets. * * @return void */ public function __clone() { foreach ($this->_columns as $k => $column) { $this->_columns[$k] = clone $column; } foreach ($this->_indexes as $k => $index) { $this->_indexes[$k] = clone $index; } foreach ($this->_fkConstraints as $k => $fk) { $this->_fkConstraints[$k] = clone $fk; $this->_fkConstraints[$k]->setLocalTable($this); } } /** * @param string[] $columnNames * @param string[] $flags * @param mixed[] $options * * @throws SchemaException */ private function _createUniqueConstraint( array $columnNames, string $indexName, array $flags = [], array $options = [] ): UniqueConstraint { if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) { throw SchemaException::indexNameInvalid($indexName); } foreach ($columnNames as $columnName) { if (! $this->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $this->_name); } } return new UniqueConstraint($indexName, $columnNames, $flags, $options); } /** * Normalizes a given identifier. * * Trims quotes and lowercases the given identifier. * * @return string The normalized identifier. */ private function normalizeIdentifier(?string $identifier): string { if ($identifier === null) { return ''; } return $this->trimQuotes(strtolower($identifier)); } public function setComment(?string $comment): self { // For keeping backward compatibility with MySQL in previous releases, table comments are stored as options. $this->addOption('comment', $comment); return $this; } public function getComment(): ?string { return $this->_options['comment'] ?? null; } } dbal/src/Schema/Constraint.php 0000644 00000002055 15021222234 0012265 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Marker interface for constraints. * * @deprecated Use {@see ForeignKeyConstraint}, {@see Index} or {@see UniqueConstraint} instead. */ interface Constraint { /** @return string */ public function getName(); /** @return string */ public function getQuotedName(AbstractPlatform $platform); /** * Returns the names of the referencing table columns * the constraint is associated with. * * @return string[] */ public function getColumns(); /** * Returns the quoted representation of the column names * the constraint is associated with. * * But only if they were defined with one or a column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedColumns(AbstractPlatform $platform); } dbal/src/Schema/AbstractAsset.php 0000644 00000014073 15021222234 0012707 0 ustar 00 <?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)); } } dbal/src/Schema/ForeignKeyConstraint.php 0000644 00000026034 15021222234 0014253 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use function array_keys; use function array_map; use function strrpos; use function strtolower; use function strtoupper; use function substr; /** * An abstraction class for a foreign key constraint. */ class ForeignKeyConstraint extends AbstractAsset implements Constraint { /** * Instance of the referencing table the foreign key constraint is associated with. * * @var Table */ protected $_localTable; /** * Asset identifier instances of the referencing table column names the foreign key constraint is associated with. * array($columnName => Identifier) * * @var Identifier[] */ protected $_localColumnNames; /** * Table or asset identifier instance of the referenced table name the foreign key constraint is associated with. * * @var Table|Identifier */ protected $_foreignTableName; /** * Asset identifier instances of the referenced table column names the foreign key constraint is associated with. * array($columnName => Identifier) * * @var Identifier[] */ protected $_foreignColumnNames; /** * Options associated with the foreign key constraint. * * @var mixed[] */ protected $_options; /** * Initializes the foreign key constraint. * * @param string[] $localColumnNames Names of the referencing table columns. * @param Table|string $foreignTableName Referenced table. * @param string[] $foreignColumnNames Names of the referenced table columns. * @param string|null $name Name of the foreign key constraint. * @param mixed[] $options Options associated with the foreign key constraint. */ public function __construct( array $localColumnNames, $foreignTableName, array $foreignColumnNames, $name = null, array $options = [] ) { if ($name !== null) { $this->_setName($name); } $this->_localColumnNames = $this->createIdentifierMap($localColumnNames); if ($foreignTableName instanceof Table) { $this->_foreignTableName = $foreignTableName; } else { $this->_foreignTableName = new Identifier($foreignTableName); } $this->_foreignColumnNames = $this->createIdentifierMap($foreignColumnNames); $this->_options = $options; } /** * @param string[] $names * * @return Identifier[] */ private function createIdentifierMap(array $names): array { $identifiers = []; foreach ($names as $name) { $identifiers[$name] = new Identifier($name); } return $identifiers; } /** * Returns the name of the referencing table * the foreign key constraint is associated with. * * @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. * * @return string */ public function getLocalTableName() { return $this->_localTable->getName(); } /** * Sets the Table instance of the referencing table * the foreign key constraint is associated with. * * @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. * * @param Table $table Instance of the referencing table. * * @return void */ public function setLocalTable(Table $table) { $this->_localTable = $table; } /** * @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. * * @return Table */ public function getLocalTable() { return $this->_localTable; } /** * Returns the names of the referencing table columns * the foreign key constraint is associated with. * * @return string[] */ public function getLocalColumns() { return array_keys($this->_localColumnNames); } /** * Returns the quoted representation of the referencing table column names * the foreign key constraint is associated with. * * But only if they were defined with one or the referencing table column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedLocalColumns(AbstractPlatform $platform) { $columns = []; foreach ($this->_localColumnNames as $column) { $columns[] = $column->getQuotedName($platform); } return $columns; } /** * Returns unquoted representation of local table column names for comparison with other FK * * @return string[] */ public function getUnquotedLocalColumns() { return array_map([$this, 'trimQuotes'], $this->getLocalColumns()); } /** * Returns unquoted representation of foreign table column names for comparison with other FK * * @return string[] */ public function getUnquotedForeignColumns() { return array_map([$this, 'trimQuotes'], $this->getForeignColumns()); } /** * {@inheritDoc} * * @deprecated Use {@see getLocalColumns()} instead. * * @see getLocalColumns */ public function getColumns() { return $this->getLocalColumns(); } /** * Returns the quoted representation of the referencing table column names * the foreign key constraint is associated with. * * But only if they were defined with one or the referencing table column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @deprecated Use {@see getQuotedLocalColumns()} instead. * * @see getQuotedLocalColumns * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedColumns(AbstractPlatform $platform) { return $this->getQuotedLocalColumns($platform); } /** * Returns the name of the referenced table * the foreign key constraint is associated with. * * @return string */ public function getForeignTableName() { return $this->_foreignTableName->getName(); } /** * Returns the non-schema qualified foreign table name. * * @return string */ public function getUnqualifiedForeignTableName() { $name = $this->_foreignTableName->getName(); $position = strrpos($name, '.'); if ($position !== false) { $name = substr($name, $position + 1); } return strtolower($name); } /** * Returns the quoted representation of the referenced table name * the foreign key constraint is associated with. * * But only if it was defined with one or the referenced table name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string */ public function getQuotedForeignTableName(AbstractPlatform $platform) { return $this->_foreignTableName->getQuotedName($platform); } /** * Returns the names of the referenced table columns * the foreign key constraint is associated with. * * @return string[] */ public function getForeignColumns() { return array_keys($this->_foreignColumnNames); } /** * Returns the quoted representation of the referenced table column names * the foreign key constraint is associated with. * * But only if they were defined with one or the referenced table column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedForeignColumns(AbstractPlatform $platform) { $columns = []; foreach ($this->_foreignColumnNames as $column) { $columns[] = $column->getQuotedName($platform); } return $columns; } /** * Returns whether or not a given option * is associated with the foreign key constraint. * * @param string $name Name of the option to check. * * @return bool */ public function hasOption($name) { return isset($this->_options[$name]); } /** * Returns an option associated with the foreign key constraint. * * @param string $name Name of the option the foreign key constraint is associated with. * * @return mixed */ public function getOption($name) { return $this->_options[$name]; } /** * Returns the options associated with the foreign key constraint. * * @return mixed[] */ public function getOptions() { return $this->_options; } /** * Returns the referential action for UPDATE operations * on the referenced table the foreign key constraint is associated with. * * @return string|null */ public function onUpdate() { return $this->onEvent('onUpdate'); } /** * Returns the referential action for DELETE operations * on the referenced table the foreign key constraint is associated with. * * @return string|null */ public function onDelete() { return $this->onEvent('onDelete'); } /** * Returns the referential action for a given database operation * on the referenced table the foreign key constraint is associated with. * * @param string $event Name of the database operation/event to return the referential action for. */ private function onEvent($event): ?string { if (isset($this->_options[$event])) { $onEvent = strtoupper($this->_options[$event]); if ($onEvent !== 'NO ACTION' && $onEvent !== 'RESTRICT') { return $onEvent; } } return null; } /** * Checks whether this foreign key constraint intersects the given index columns. * * Returns `true` if at least one of this foreign key's local columns * matches one of the given index's columns, `false` otherwise. * * @param Index $index The index to be checked against. * * @return bool */ public function intersectsIndexColumns(Index $index) { foreach ($index->getColumns() as $indexColumn) { foreach ($this->_localColumnNames as $localColumn) { if (strtolower($indexColumn) === strtolower($localColumn->getName())) { return true; } } } return false; } } dbal/src/Schema/Sequence.php 0000644 00000006441 15021222234 0011714 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\Deprecations\Deprecation; use function count; use function sprintf; /** * Sequence structure. */ class Sequence extends AbstractAsset { /** @var int */ protected $allocationSize = 1; /** @var int */ protected $initialValue = 1; /** @var int|null */ protected $cache; /** * @param string $name * @param int $allocationSize * @param int $initialValue * @param int|null $cache */ public function __construct($name, $allocationSize = 1, $initialValue = 1, $cache = null) { $this->_setName($name); $this->setAllocationSize($allocationSize); $this->setInitialValue($initialValue); $this->cache = $cache; } /** @return int */ public function getAllocationSize() { return $this->allocationSize; } /** @return int */ public function getInitialValue() { return $this->initialValue; } /** @return int|null */ public function getCache() { return $this->cache; } /** * @param int $allocationSize * * @return Sequence */ public function setAllocationSize($allocationSize) { if ($allocationSize > 0) { $this->allocationSize = $allocationSize; } else { $this->allocationSize = 1; } return $this; } /** * @param int $initialValue * * @return Sequence */ public function setInitialValue($initialValue) { if ($initialValue > 0) { $this->initialValue = $initialValue; } else { $this->initialValue = 1; } return $this; } /** * @param int $cache * * @return Sequence */ public function setCache($cache) { $this->cache = $cache; return $this; } /** * Checks if this sequence is an autoincrement sequence for a given table. * * This is used inside the comparator to not report sequences as missing, * when the "from" schema implicitly creates the sequences. * * @return bool */ public function isAutoIncrementsFor(Table $table) { $primaryKey = $table->getPrimaryKey(); if ($primaryKey === null) { return false; } $pkColumns = $primaryKey->getColumns(); if (count($pkColumns) !== 1) { return false; } $column = $table->getColumn($pkColumns[0]); if (! $column->getAutoincrement()) { return false; } $sequenceName = $this->getShortestName($table->getNamespaceName()); $tableName = $table->getShortestName($table->getNamespaceName()); $tableSequenceName = sprintf('%s_%s_seq', $tableName, $column->getShortestName($table->getNamespaceName())); return $tableSequenceName === $sequenceName; } /** * @deprecated * * @return void */ public function visit(Visitor $visitor) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5435', 'Sequence::visit() is deprecated.', ); $visitor->acceptSequence($this); } } dbal/src/SQL/Builder/SelectSQLBuilder.php 0000644 00000000346 15021222234 0014075 0 ustar 00 <?php namespace Doctrine\DBAL\SQL\Builder; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Query\SelectQuery; interface SelectSQLBuilder { /** @throws Exception */ public function buildSQL(SelectQuery $query): string; } dbal/src/SQL/Builder/CreateSchemaObjectsSQLBuilder.php 0000644 00000003630 15021222234 0016513 0 ustar 00 <?php namespace Doctrine\DBAL\SQL\Builder; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use function array_merge; final class CreateSchemaObjectsSQLBuilder { private AbstractPlatform $platform; public function __construct(AbstractPlatform $platform) { $this->platform = $platform; } /** * @return list<string> * * @throws Exception */ public function buildSQL(Schema $schema): array { return array_merge( $this->buildNamespaceStatements($schema->getNamespaces()), $this->buildSequenceStatements($schema->getSequences()), $this->buildTableStatements($schema->getTables()), ); } /** * @param list<string> $namespaces * * @return list<string> * * @throws Exception */ private function buildNamespaceStatements(array $namespaces): array { $statements = []; if ($this->platform->supportsSchemas()) { foreach ($namespaces as $namespace) { $statements[] = $this->platform->getCreateSchemaSQL($namespace); } } return $statements; } /** * @param list<Table> $tables * * @return list<string> * * @throws Exception */ private function buildTableStatements(array $tables): array { return $this->platform->getCreateTablesSQL($tables); } /** * @param list<Sequence> $sequences * * @return list<string> * * @throws Exception */ private function buildSequenceStatements(array $sequences): array { $statements = []; foreach ($sequences as $sequence) { $statements[] = $this->platform->getCreateSequenceSQL($sequence); } return $statements; } } dbal/src/SQL/Builder/DefaultSelectSQLBuilder.php 0000644 00000004772 15021222234 0015411 0 ustar 00 <?php namespace Doctrine\DBAL\SQL\Builder; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode; use Doctrine\DBAL\Query\SelectQuery; use function count; use function implode; final class DefaultSelectSQLBuilder implements SelectSQLBuilder { private AbstractPlatform $platform; private ?string $forUpdateSQL; private ?string $skipLockedSQL; /** @internal The SQL builder should be instantiated only by database platforms. */ public function __construct(AbstractPlatform $platform, ?string $forUpdateSQL, ?string $skipLockedSQL) { $this->platform = $platform; $this->forUpdateSQL = $forUpdateSQL; $this->skipLockedSQL = $skipLockedSQL; } /** @throws Exception */ public function buildSQL(SelectQuery $query): string { $parts = ['SELECT']; if ($query->isDistinct()) { $parts[] = 'DISTINCT'; } $parts[] = implode(', ', $query->getColumns()); $from = $query->getFrom(); if (count($from) > 0) { $parts[] = 'FROM ' . implode(', ', $from); } $where = $query->getWhere(); if ($where !== null) { $parts[] = 'WHERE ' . $where; } $groupBy = $query->getGroupBy(); if (count($groupBy) > 0) { $parts[] = 'GROUP BY ' . implode(', ', $groupBy); } $having = $query->getHaving(); if ($having !== null) { $parts[] = 'HAVING ' . $having; } $orderBy = $query->getOrderBy(); if (count($orderBy) > 0) { $parts[] = 'ORDER BY ' . implode(', ', $orderBy); } $sql = implode(' ', $parts); $limit = $query->getLimit(); if ($limit->isDefined()) { $sql = $this->platform->modifyLimitQuery($sql, $limit->getMaxResults(), $limit->getFirstResult()); } $forUpdate = $query->getForUpdate(); if ($forUpdate !== null) { if ($this->forUpdateSQL === null) { throw Exception::notSupported('FOR UPDATE'); } $sql .= ' ' . $this->forUpdateSQL; if ($forUpdate->getConflictResolutionMode() === ConflictResolutionMode::SKIP_LOCKED) { if ($this->skipLockedSQL === null) { throw Exception::notSupported('SKIP LOCKED'); } $sql .= ' ' . $this->skipLockedSQL; } } return $sql; } } dbal/src/SQL/Builder/DropSchemaObjectsSQLBuilder.php 0000644 00000002527 15021222234 0016220 0 ustar 00 <?php namespace Doctrine\DBAL\SQL\Builder; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use function array_merge; final class DropSchemaObjectsSQLBuilder { private AbstractPlatform $platform; public function __construct(AbstractPlatform $platform) { $this->platform = $platform; } /** * @return list<string> * * @throws Exception */ public function buildSQL(Schema $schema): array { return array_merge( $this->buildSequenceStatements($schema->getSequences()), $this->buildTableStatements($schema->getTables()), ); } /** * @param list<Table> $tables * * @return list<string> */ private function buildTableStatements(array $tables): array { return $this->platform->getDropTablesSQL($tables); } /** * @param list<Sequence> $sequences * * @return list<string> * * @throws Exception */ private function buildSequenceStatements(array $sequences): array { $statements = []; foreach ($sequences as $sequence) { $statements[] = $this->platform->getDropSequenceSQL($sequence); } return $statements; } } dbal/src/SQL/Parser/Visitor.php 0000644 00000000765 15021222234 0012301 0 ustar 00 <?php namespace Doctrine\DBAL\SQL\Parser; /** * SQL parser visitor * * @internal */ interface Visitor { /** * Accepts an SQL fragment containing a positional parameter */ public function acceptPositionalParameter(string $sql): void; /** * Accepts an SQL fragment containing a named parameter */ public function acceptNamedParameter(string $sql): void; /** * Accepts other SQL fragments */ public function acceptOther(string $sql): void; } dbal/src/SQL/Parser/Exception.php 0000644 00000000200 15021222234 0012560 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\SQL\Parser; use Throwable; interface Exception extends Throwable { } dbal/src/SQL/Parser/Exception/RegularExpressionError.php 0000644 00000000625 15021222234 0017266 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\SQL\Parser\Exception; use Doctrine\DBAL\SQL\Parser\Exception; use RuntimeException; use function preg_last_error; use function preg_last_error_msg; class RegularExpressionError extends RuntimeException implements Exception { public static function new(): self { return new self(preg_last_error_msg(), preg_last_error()); } } dbal/src/SQL/Parser.php 0000644 00000010115 15021222234 0010630 0 ustar 00 <?php namespace Doctrine\DBAL\SQL; use Doctrine\DBAL\SQL\Parser\Exception; use Doctrine\DBAL\SQL\Parser\Exception\RegularExpressionError; use Doctrine\DBAL\SQL\Parser\Visitor; use function array_merge; use function assert; use function current; use function implode; use function key; use function next; use function preg_last_error; use function preg_match; use function reset; use function sprintf; use function strlen; use const PREG_NO_ERROR; /** * The SQL parser that focuses on identifying prepared statement parameters. It implements parsing other tokens like * string literals and comments only as a way to not confuse their contents with the the parameter placeholders. * * The parsing logic and the implementation is inspired by the PHP PDO parser. * * @internal * * @see https://github.com/php/php-src/blob/php-7.4.12/ext/pdo/pdo_sql_parser.re#L49-L69 */ final class Parser { private const SPECIAL_CHARS = ':\?\'"`\\[\\-\\/'; private const BACKTICK_IDENTIFIER = '`[^`]*`'; private const BRACKET_IDENTIFIER = '(?<!\b(?i:ARRAY))\[(?:[^\]])*\]'; private const MULTICHAR = ':{2,}'; private const NAMED_PARAMETER = ':[a-zA-Z0-9_]+'; private const POSITIONAL_PARAMETER = '(?<!\\?)\\?(?!\\?)'; private const ONE_LINE_COMMENT = '--[^\r\n]*'; private const MULTI_LINE_COMMENT = '/\*([^*]+|\*+[^/*])*\**\*/'; private const SPECIAL = '[' . self::SPECIAL_CHARS . ']'; private const OTHER = '[^' . self::SPECIAL_CHARS . ']+'; private string $sqlPattern; public function __construct(bool $mySQLStringEscaping) { if ($mySQLStringEscaping) { $patterns = [ $this->getMySQLStringLiteralPattern("'"), $this->getMySQLStringLiteralPattern('"'), ]; } else { $patterns = [ $this->getAnsiSQLStringLiteralPattern("'"), $this->getAnsiSQLStringLiteralPattern('"'), ]; } $patterns = array_merge($patterns, [ self::BACKTICK_IDENTIFIER, self::BRACKET_IDENTIFIER, self::MULTICHAR, self::ONE_LINE_COMMENT, self::MULTI_LINE_COMMENT, self::OTHER, ]); $this->sqlPattern = sprintf('(%s)', implode('|', $patterns)); } /** * Parses the given SQL statement * * @throws Exception */ public function parse(string $sql, Visitor $visitor): void { /** @var array<string,callable> $patterns */ $patterns = [ self::NAMED_PARAMETER => static function (string $sql) use ($visitor): void { $visitor->acceptNamedParameter($sql); }, self::POSITIONAL_PARAMETER => static function (string $sql) use ($visitor): void { $visitor->acceptPositionalParameter($sql); }, $this->sqlPattern => static function (string $sql) use ($visitor): void { $visitor->acceptOther($sql); }, self::SPECIAL => static function (string $sql) use ($visitor): void { $visitor->acceptOther($sql); }, ]; $offset = 0; while (($handler = current($patterns)) !== false) { if (preg_match('~\G' . key($patterns) . '~s', $sql, $matches, 0, $offset) === 1) { $handler($matches[0]); reset($patterns); $offset += strlen($matches[0]); } elseif (preg_last_error() !== PREG_NO_ERROR) { // @codeCoverageIgnoreStart throw RegularExpressionError::new(); // @codeCoverageIgnoreEnd } else { next($patterns); } } assert($offset === strlen($sql)); } private function getMySQLStringLiteralPattern(string $delimiter): string { return $delimiter . '((\\\\.)|(?![' . $delimiter . '\\\\]).)*' . $delimiter; } private function getAnsiSQLStringLiteralPattern(string $delimiter): string { return $delimiter . '[^' . $delimiter . ']*' . $delimiter; } } dbal/src/Connection.php 0000644 00000177133 15021222234 0011052 0 ustar 00 <?php namespace Doctrine\DBAL; use Closure; use Doctrine\Common\EventManager; use Doctrine\DBAL\Cache\ArrayResult; use Doctrine\DBAL\Cache\CacheException; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Driver\Connection as DriverConnection; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\Event\TransactionBeginEventArgs; use Doctrine\DBAL\Event\TransactionCommitEventArgs; use Doctrine\DBAL\Event\TransactionRollBackEventArgs; use Doctrine\DBAL\Exception\ConnectionLost; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\InvalidArgumentException; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Query\Expression\ExpressionBuilder; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Schema\LegacySchemaManagerFactory; use Doctrine\DBAL\Schema\SchemaManagerFactory; use Doctrine\DBAL\SQL\Parser; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use LogicException; use SensitiveParameter; use Throwable; use Traversable; use function array_key_exists; use function assert; use function count; use function get_class; use function implode; use function is_array; use function is_int; use function is_string; use function key; use function method_exists; use function sprintf; /** * A database abstraction-level connection that implements features like events, transaction isolation levels, * configuration, emulated transaction nesting, lazy connecting and more. * * @psalm-import-type Params from DriverManager * @psalm-consistent-constructor */ class Connection { /** * Represents an array of ints to be expanded by Doctrine SQL parsing. * * @deprecated Use {@see ArrayParameterType::INTEGER} instead. */ public const PARAM_INT_ARRAY = ArrayParameterType::INTEGER; /** * Represents an array of strings to be expanded by Doctrine SQL parsing. * * @deprecated Use {@see ArrayParameterType::STRING} instead. */ public const PARAM_STR_ARRAY = ArrayParameterType::STRING; /** * Represents an array of ascii strings to be expanded by Doctrine SQL parsing. * * @deprecated Use {@see ArrayParameterType::ASCII} instead. */ public const PARAM_ASCII_STR_ARRAY = ArrayParameterType::ASCII; /** * Offset by which PARAM_* constants are detected as arrays of the param type. * * @internal Should be used only within the wrapper layer. */ public const ARRAY_PARAM_OFFSET = 100; /** * The wrapped driver connection. * * @var DriverConnection|null */ protected $_conn; /** @var Configuration */ protected $_config; /** * @deprecated * * @var EventManager */ protected $_eventManager; /** * @deprecated Use {@see createExpressionBuilder()} instead. * * @var ExpressionBuilder */ protected $_expr; /** * The current auto-commit mode of this connection. */ private bool $autoCommit = true; /** * The transaction nesting level. */ private int $transactionNestingLevel = 0; /** * The currently active transaction isolation level or NULL before it has been determined. * * @var TransactionIsolationLevel::*|null */ private $transactionIsolationLevel; /** * If nested transactions should use savepoints. */ private bool $nestTransactionsWithSavepoints = false; /** * The parameters used during creation of the Connection instance. * * @var array<string,mixed> * @psalm-var Params */ private array $params; /** * The database platform object used by the connection or NULL before it's initialized. */ private ?AbstractPlatform $platform = null; private ?ExceptionConverter $exceptionConverter = null; private ?Parser $parser = null; /** * The schema manager. * * @deprecated Use {@see createSchemaManager()} instead. * * @var AbstractSchemaManager|null */ protected $_schemaManager; /** * The used DBAL driver. * * @var Driver */ protected $_driver; /** * Flag that indicates whether the current transaction is marked for rollback only. */ private bool $isRollbackOnly = false; private SchemaManagerFactory $schemaManagerFactory; /** * Initializes a new instance of the Connection class. * * @internal The connection can be only instantiated by the driver manager. * * @param array<string,mixed> $params The connection parameters. * @param Driver $driver The driver to use. * @param Configuration|null $config The configuration, optional. * @param EventManager|null $eventManager The event manager, optional. * @psalm-param Params $params * * @throws Exception */ public function __construct( #[SensitiveParameter] array $params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null ) { $this->_driver = $driver; $this->params = $params; // Create default config and event manager if none given $config ??= new Configuration(); $eventManager ??= new EventManager(); $this->_config = $config; $this->_eventManager = $eventManager; if (isset($params['platform'])) { if (! $params['platform'] instanceof Platforms\AbstractPlatform) { throw Exception::invalidPlatformType($params['platform']); } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5699', 'The "platform" connection parameter is deprecated.' . ' Use a driver middleware that would instantiate the platform instead.', ); $this->platform = $params['platform']; $this->platform->setEventManager($this->_eventManager); $this->platform->setDisableTypeComments($config->getDisableTypeComments()); } $this->_expr = $this->createExpressionBuilder(); $this->autoCommit = $config->getAutoCommit(); $schemaManagerFactory = $config->getSchemaManagerFactory(); if ($schemaManagerFactory === null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5812', 'Not configuring a schema manager factory is deprecated.' . ' Use %s which is going to be the default in DBAL 4.', DefaultSchemaManagerFactory::class, ); $schemaManagerFactory = new LegacySchemaManagerFactory(); } $this->schemaManagerFactory = $schemaManagerFactory; } /** * Gets the parameters used during instantiation. * * @internal * * @return array<string,mixed> * @psalm-return Params */ public function getParams() { return $this->params; } /** * Gets the name of the currently selected database. * * @return string|null The name of the database or NULL if a database is not selected. * The platforms which don't support the concept of a database (e.g. embedded databases) * must always return a string as an indicator of an implicitly selected database. * * @throws Exception */ public function getDatabase() { $platform = $this->getDatabasePlatform(); $query = $platform->getDummySelectSQL($platform->getCurrentDatabaseExpression()); $database = $this->fetchOne($query); assert(is_string($database) || $database === null); return $database; } /** * Gets the DBAL driver instance. * * @return Driver */ public function getDriver() { return $this->_driver; } /** * Gets the Configuration used by the Connection. * * @return Configuration */ public function getConfiguration() { return $this->_config; } /** * Gets the EventManager used by the Connection. * * @deprecated * * @return EventManager */ public function getEventManager() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', '%s is deprecated.', __METHOD__, ); return $this->_eventManager; } /** * Gets the DatabasePlatform for the connection. * * @return AbstractPlatform * * @throws Exception */ public function getDatabasePlatform() { if ($this->platform === null) { $this->platform = $this->detectDatabasePlatform(); $this->platform->setEventManager($this->_eventManager); $this->platform->setDisableTypeComments($this->_config->getDisableTypeComments()); } return $this->platform; } /** * Creates an expression builder for the connection. */ public function createExpressionBuilder(): ExpressionBuilder { return new ExpressionBuilder($this); } /** * Gets the ExpressionBuilder for the connection. * * @deprecated Use {@see createExpressionBuilder()} instead. * * @return ExpressionBuilder */ public function getExpressionBuilder() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4515', 'Connection::getExpressionBuilder() is deprecated,' . ' use Connection::createExpressionBuilder() instead.', ); return $this->_expr; } /** * Establishes the connection with the database. * * @internal This method will be made protected in DBAL 4.0. * * @return bool TRUE if the connection was successfully established, FALSE if * the connection is already open. * * @throws Exception * * @psalm-assert !null $this->_conn */ public function connect() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4966', 'Public access to Connection::connect() is deprecated.', ); if ($this->_conn !== null) { return false; } try { $this->_conn = $this->_driver->connect($this->params); } catch (Driver\Exception $e) { throw $this->convertException($e); } if ($this->autoCommit === false) { $this->beginTransaction(); } if ($this->_eventManager->hasListeners(Events::postConnect)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated. Implement a middleware instead.', Events::postConnect, ); $eventArgs = new Event\ConnectionEventArgs($this); $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); } return true; } /** * Detects and sets the database platform. * * Evaluates custom platform class and version in order to set the correct platform. * * @throws Exception If an invalid platform was specified for this connection. */ private function detectDatabasePlatform(): AbstractPlatform { $version = $this->getDatabasePlatformVersion(); if ($version !== null) { assert($this->_driver instanceof VersionAwarePlatformDriver); return $this->_driver->createDatabasePlatformForVersion($version); } return $this->_driver->getDatabasePlatform(); } /** * Returns the version of the related platform if applicable. * * Returns null if either the driver is not capable to create version * specific platform instances, no explicit server version was specified * or the underlying driver connection cannot determine the platform * version without having to query it (performance reasons). * * @return string|null * * @throws Throwable */ private function getDatabasePlatformVersion() { // Driver does not support version specific platforms. if (! $this->_driver instanceof VersionAwarePlatformDriver) { return null; } // Explicit platform version requested (supersedes auto-detection). if (isset($this->params['serverVersion'])) { return $this->params['serverVersion']; } if (isset($this->params['primary']) && isset($this->params['primary']['serverVersion'])) { return $this->params['primary']['serverVersion']; } // If not connected, we need to connect now to determine the platform version. if ($this->_conn === null) { try { $this->connect(); } catch (Exception $originalException) { if (! isset($this->params['dbname'])) { throw $originalException; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5707', 'Relying on a fallback connection used to determine the database platform while connecting' . ' to a non-existing database is deprecated. Either use an existing database name in' . ' connection parameters or omit the database name if the platform' . ' and the server configuration allow that.', ); // The database to connect to might not yet exist. // Retry detection without database name connection parameter. $params = $this->params; unset($this->params['dbname']); try { $this->connect(); } catch (Exception $fallbackException) { // Either the platform does not support database-less connections // or something else went wrong. throw $originalException; } finally { $this->params = $params; } $serverVersion = $this->getServerVersion(); // Close "temporary" connection to allow connecting to the real database again. $this->close(); return $serverVersion; } } return $this->getServerVersion(); } /** * Returns the database server version if the underlying driver supports it. * * @return string|null * * @throws Exception */ private function getServerVersion() { $connection = $this->getWrappedConnection(); // Automatic platform version detection. if ($connection instanceof ServerInfoAwareConnection) { try { return $connection->getServerVersion(); } catch (Driver\Exception $e) { throw $this->convertException($e); } } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4750', 'Not implementing the ServerInfoAwareConnection interface in %s is deprecated', get_class($connection), ); // Unable to detect platform version. return null; } /** * Returns the current auto-commit mode for this connection. * * @see setAutoCommit * * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise. */ public function isAutoCommit() { return $this->autoCommit === true; } /** * Sets auto-commit mode for this connection. * * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either * the method commit or the method rollback. By default, new connections are in auto-commit mode. * * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op. * * @see isAutoCommit * * @param bool $autoCommit True to enable auto-commit mode; false to disable it. * * @return void */ public function setAutoCommit($autoCommit) { $autoCommit = (bool) $autoCommit; // Mode not changed, no-op. if ($autoCommit === $this->autoCommit) { return; } $this->autoCommit = $autoCommit; // Commit all currently active transactions if any when switching auto-commit mode. if ($this->_conn === null || $this->transactionNestingLevel === 0) { return; } $this->commitAll(); } /** * Prepares and executes an SQL query and returns the first row of the result * as an associative array. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return array<string, mixed>|false False is returned if no rows are found. * * @throws Exception */ public function fetchAssociative(string $query, array $params = [], array $types = []) { return $this->executeQuery($query, $params, $types)->fetchAssociative(); } /** * Prepares and executes an SQL query and returns the first row of the result * as a numerically indexed array. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return list<mixed>|false False is returned if no rows are found. * * @throws Exception */ public function fetchNumeric(string $query, array $params = [], array $types = []) { return $this->executeQuery($query, $params, $types)->fetchNumeric(); } /** * Prepares and executes an SQL query and returns the value of a single column * of the first row of the result. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return mixed|false False is returned if no rows are found. * * @throws Exception */ public function fetchOne(string $query, array $params = [], array $types = []) { return $this->executeQuery($query, $params, $types)->fetchOne(); } /** * Whether an actual connection to the database is established. * * @return bool */ public function isConnected() { return $this->_conn !== null; } /** * Checks whether a transaction is currently active. * * @return bool TRUE if a transaction is currently active, FALSE otherwise. */ public function isTransactionActive() { return $this->transactionNestingLevel > 0; } /** * Adds condition based on the criteria to the query components * * @param array<string,mixed> $criteria Map of key columns to their values * @param string[] $columns Column names * @param mixed[] $values Column values * @param string[] $conditions Key conditions * * @throws Exception */ private function addCriteriaCondition( array $criteria, array &$columns, array &$values, array &$conditions ): void { $platform = $this->getDatabasePlatform(); foreach ($criteria as $columnName => $value) { if ($value === null) { $conditions[] = $platform->getIsNullExpression($columnName); continue; } $columns[] = $columnName; $values[] = $value; $conditions[] = $columnName . ' = ?'; } } /** * Executes an SQL DELETE statement on a table. * * Table expression and columns are not escaped and are not safe for user-input. * * @param string $table Table name * @param array<string, mixed> $criteria Deletion criteria * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return int|string The number of affected rows. * * @throws Exception */ public function delete($table, array $criteria, array $types = []) { if (count($criteria) === 0) { throw InvalidArgumentException::fromEmptyCriteria(); } $columns = $values = $conditions = []; $this->addCriteriaCondition($criteria, $columns, $values, $conditions); return $this->executeStatement( 'DELETE FROM ' . $table . ' WHERE ' . implode(' AND ', $conditions), $values, is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types, ); } /** * Closes the connection. * * @return void */ public function close() { $this->_conn = null; $this->transactionNestingLevel = 0; } /** * Sets the transaction isolation level. * * @param TransactionIsolationLevel::* $level The level to set. * * @return int|string * * @throws Exception */ public function setTransactionIsolation($level) { $this->transactionIsolationLevel = $level; return $this->executeStatement($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level)); } /** * Gets the currently active transaction isolation level. * * @return TransactionIsolationLevel::* The current transaction isolation level. * * @throws Exception */ public function getTransactionIsolation() { return $this->transactionIsolationLevel ??= $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel(); } /** * Executes an SQL UPDATE statement on a table. * * Table expression and columns are not escaped and are not safe for user-input. * * @param string $table Table name * @param array<string, mixed> $data Column-value pairs * @param array<string, mixed> $criteria Update criteria * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return int|string The number of affected rows. * * @throws Exception */ public function update($table, array $data, array $criteria, array $types = []) { $columns = $values = $conditions = $set = []; foreach ($data as $columnName => $value) { $columns[] = $columnName; $values[] = $value; $set[] = $columnName . ' = ?'; } $this->addCriteriaCondition($criteria, $columns, $values, $conditions); if (is_string(key($types))) { $types = $this->extractTypeValues($columns, $types); } $sql = 'UPDATE ' . $table . ' SET ' . implode(', ', $set) . ' WHERE ' . implode(' AND ', $conditions); return $this->executeStatement($sql, $values, $types); } /** * Inserts a table row with specified data. * * Table expression and columns are not escaped and are not safe for user-input. * * @param string $table Table name * @param array<string, mixed> $data Column-value pairs * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return int|string The number of affected rows. * * @throws Exception */ public function insert($table, array $data, array $types = []) { if (count($data) === 0) { return $this->executeStatement('INSERT INTO ' . $table . ' () VALUES ()'); } $columns = []; $values = []; $set = []; foreach ($data as $columnName => $value) { $columns[] = $columnName; $values[] = $value; $set[] = '?'; } return $this->executeStatement( 'INSERT INTO ' . $table . ' (' . implode(', ', $columns) . ')' . ' VALUES (' . implode(', ', $set) . ')', $values, is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types, ); } /** * Extract ordered type list from an ordered column list and type map. * * @param array<int, string> $columnList * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types * * @return array<int, int|string|Type|null>|array<string, int|string|Type|null> */ private function extractTypeValues(array $columnList, array $types): array { $typeValues = []; foreach ($columnList as $columnName) { $typeValues[] = $types[$columnName] ?? ParameterType::STRING; } return $typeValues; } /** * Quotes a string so it can be safely used as a table or column name, even if * it is a reserved name. * * Delimiting style depends on the underlying database platform that is being used. * * NOTE: Just because you CAN use quoted identifiers does not mean * you SHOULD use them. In general, they end up causing way more * problems than they solve. * * @param string $str The name to be quoted. * * @return string The quoted name. */ public function quoteIdentifier($str) { return $this->getDatabasePlatform()->quoteIdentifier($str); } /** * The usage of this method is discouraged. Use prepared statements * or {@see AbstractPlatform::quoteStringLiteral()} instead. * * @param mixed $value * @param int|string|Type|null $type * * @return mixed */ public function quote($value, $type = ParameterType::STRING) { $connection = $this->getWrappedConnection(); [$value, $bindingType] = $this->getBindingInfo($value, $type); return $connection->quote($value, $bindingType); } /** * Prepares and executes an SQL query and returns the result as an array of numeric arrays. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return list<list<mixed>> * * @throws Exception */ public function fetchAllNumeric(string $query, array $params = [], array $types = []): array { return $this->executeQuery($query, $params, $types)->fetchAllNumeric(); } /** * Prepares and executes an SQL query and returns the result as an array of associative arrays. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return list<array<string,mixed>> * * @throws Exception */ public function fetchAllAssociative(string $query, array $params = [], array $types = []): array { return $this->executeQuery($query, $params, $types)->fetchAllAssociative(); } /** * Prepares and executes an SQL query and returns the result as an associative array with the keys * mapped to the first column and the values mapped to the second column. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return array<mixed,mixed> * * @throws Exception */ public function fetchAllKeyValue(string $query, array $params = [], array $types = []): array { return $this->executeQuery($query, $params, $types)->fetchAllKeyValue(); } /** * Prepares and executes an SQL query and returns the result as 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. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return array<mixed,array<string,mixed>> * * @throws Exception */ public function fetchAllAssociativeIndexed(string $query, array $params = [], array $types = []): array { return $this->executeQuery($query, $params, $types)->fetchAllAssociativeIndexed(); } /** * Prepares and executes an SQL query and returns the result as an array of the first column values. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return list<mixed> * * @throws Exception */ public function fetchFirstColumn(string $query, array $params = [], array $types = []): array { return $this->executeQuery($query, $params, $types)->fetchFirstColumn(); } /** * Prepares and executes an SQL query and returns the result as an iterator over rows represented as numeric arrays. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return Traversable<int,list<mixed>> * * @throws Exception */ public function iterateNumeric(string $query, array $params = [], array $types = []): Traversable { return $this->executeQuery($query, $params, $types)->iterateNumeric(); } /** * Prepares and executes an SQL query and returns the result as an iterator over rows represented * as associative arrays. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return Traversable<int,array<string,mixed>> * * @throws Exception */ public function iterateAssociative(string $query, array $params = [], array $types = []): Traversable { return $this->executeQuery($query, $params, $types)->iterateAssociative(); } /** * Prepares and executes an SQL query and returns the result as an iterator with the keys * mapped to the first column and the values mapped to the second column. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return Traversable<mixed,mixed> * * @throws Exception */ public function iterateKeyValue(string $query, array $params = [], array $types = []): Traversable { return $this->executeQuery($query, $params, $types)->iterateKeyValue(); } /** * Prepares and executes an SQL query and returns the result as an iterator with the keys mapped * to the first column and the values being an associative array representing the rest of the columns * and their values. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string>|array<string, int|string> $types Parameter types * * @return Traversable<mixed,array<string,mixed>> * * @throws Exception */ public function iterateAssociativeIndexed(string $query, array $params = [], array $types = []): Traversable { return $this->executeQuery($query, $params, $types)->iterateAssociativeIndexed(); } /** * Prepares and executes an SQL query and returns the result as an iterator over the first column values. * * @param string $query SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return Traversable<int,mixed> * * @throws Exception */ public function iterateColumn(string $query, array $params = [], array $types = []): Traversable { return $this->executeQuery($query, $params, $types)->iterateColumn(); } /** * Prepares an SQL statement. * * @param string $sql The SQL statement to prepare. * * @throws Exception */ public function prepare(string $sql): Statement { $connection = $this->getWrappedConnection(); try { $statement = $connection->prepare($sql); } catch (Driver\Exception $e) { throw $this->convertExceptionDuringQuery($e, $sql); } return new Statement($this, $statement, $sql); } /** * Executes an, optionally parameterized, SQL query. * * If the query is parametrized, a prepared statement is used. * If an SQLLogger is configured, the execution is logged. * * @param string $sql SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @throws Exception */ public function executeQuery( string $sql, array $params = [], $types = [], ?QueryCacheProfile $qcp = null ): Result { if ($qcp !== null) { return $this->executeCacheQuery($sql, $params, $types, $qcp); } $connection = $this->getWrappedConnection(); $logger = $this->_config->getSQLLogger(); if ($logger !== null) { $logger->startQuery($sql, $params, $types); } try { if (count($params) > 0) { if ($this->needsArrayParameterConversion($params, $types)) { [$sql, $params, $types] = $this->expandArrayParameters($sql, $params, $types); } $stmt = $connection->prepare($sql); $this->bindParameters($stmt, $params, $types); $result = $stmt->execute(); } else { $result = $connection->query($sql); } return new Result($result, $this); } catch (Driver\Exception $e) { throw $this->convertExceptionDuringQuery($e, $sql, $params, $types); } finally { if ($logger !== null) { $logger->stopQuery(); } } } /** * Executes a caching query. * * @param string $sql SQL query * @param list<mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @throws CacheException * @throws Exception */ public function executeCacheQuery($sql, $params, $types, QueryCacheProfile $qcp): Result { $resultCache = $qcp->getResultCache() ?? $this->_config->getResultCache(); if ($resultCache === null) { throw CacheException::noResultDriverConfigured(); } $connectionParams = $this->params; unset($connectionParams['platform'], $connectionParams['password'], $connectionParams['url']); [$cacheKey, $realKey] = $qcp->generateCacheKeys($sql, $params, $types, $connectionParams); $item = $resultCache->getItem($cacheKey); if ($item->isHit()) { $value = $item->get(); if (! is_array($value)) { $value = []; } if (isset($value[$realKey])) { return new Result(new ArrayResult($value[$realKey]), $this); } } else { $value = []; } $data = $this->fetchAllAssociative($sql, $params, $types); $value[$realKey] = $data; $item->set($value); $lifetime = $qcp->getLifetime(); if ($lifetime > 0) { $item->expiresAfter($lifetime); } $resultCache->save($item); return new Result(new ArrayResult($data), $this); } /** * Executes an SQL statement with the given parameters and returns the number of affected rows. * * Could be used for: * - DML statements: INSERT, UPDATE, DELETE, etc. * - DDL statements: CREATE, DROP, ALTER, etc. * - DCL statements: GRANT, REVOKE, etc. * - Session control statements: ALTER SESSION, SET, DECLARE, etc. * - Other statements that don't yield a row set. * * This method supports PDO binding types as well as DBAL mapping types. * * @param string $sql SQL statement * @param list<mixed>|array<string, mixed> $params Statement parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return int|string The number of affected rows. * * @throws Exception */ public function executeStatement($sql, array $params = [], array $types = []) { $connection = $this->getWrappedConnection(); $logger = $this->_config->getSQLLogger(); if ($logger !== null) { $logger->startQuery($sql, $params, $types); } try { if (count($params) > 0) { if ($this->needsArrayParameterConversion($params, $types)) { [$sql, $params, $types] = $this->expandArrayParameters($sql, $params, $types); } $stmt = $connection->prepare($sql); $this->bindParameters($stmt, $params, $types); return $stmt->execute() ->rowCount(); } return $connection->exec($sql); } catch (Driver\Exception $e) { throw $this->convertExceptionDuringQuery($e, $sql, $params, $types); } finally { if ($logger !== null) { $logger->stopQuery(); } } } /** * Returns the current transaction nesting level. * * @return int The nesting level. A value of 0 means there's no active transaction. */ public function getTransactionNestingLevel() { return $this->transactionNestingLevel; } /** * Returns the ID of the last inserted row, or the last value from a sequence object, * depending on the underlying driver. * * Note: This method may not return a meaningful or consistent result across different drivers, * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY * columns or sequences. * * @param string|null $name Name of the sequence object from which the ID should be returned. * * @return string|int|false A string representation of the last inserted ID. * * @throws Exception */ public function lastInsertId($name = null) { if ($name !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); } try { return $this->getWrappedConnection()->lastInsertId($name); } catch (Driver\Exception $e) { throw $this->convertException($e); } } /** * Executes a function in a transaction. * * The function gets passed this Connection instance as an (optional) parameter. * * If an exception occurs during execution of the function or transaction commit, * the transaction is rolled back and the exception re-thrown. * * @param Closure(self):T $func The function to execute transactionally. * * @return T The value returned by $func * * @throws Throwable * * @template T */ public function transactional(Closure $func) { $this->beginTransaction(); try { $res = $func($this); $this->commit(); return $res; } catch (Throwable $e) { $this->rollBack(); throw $e; } } /** * Sets if nested transactions should use savepoints. * * @param bool $nestTransactionsWithSavepoints * * @return void * * @throws Exception */ public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints) { if (! $nestTransactionsWithSavepoints) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5383', <<<'DEPRECATION' Nesting transactions without enabling savepoints is deprecated. Call %s::setNestTransactionsWithSavepoints(true) to enable savepoints. DEPRECATION, self::class, ); } if ($this->transactionNestingLevel > 0) { throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction(); } $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints; } /** * Gets if nested transactions should use savepoints. * * @return bool */ public function getNestTransactionsWithSavepoints() { return $this->nestTransactionsWithSavepoints; } /** * Returns the savepoint name to use for nested transactions. * * @return string */ protected function _getNestedTransactionSavePointName() { return 'DOCTRINE_' . $this->transactionNestingLevel; } /** * @return bool * * @throws Exception */ public function beginTransaction() { $connection = $this->getWrappedConnection(); ++$this->transactionNestingLevel; $logger = $this->_config->getSQLLogger(); if ($this->transactionNestingLevel === 1) { if ($logger !== null) { $logger->startQuery('"START TRANSACTION"'); } $connection->beginTransaction(); if ($logger !== null) { $logger->stopQuery(); } } elseif ($this->nestTransactionsWithSavepoints) { if ($logger !== null) { $logger->startQuery('"SAVEPOINT"'); } $this->createSavepoint($this->_getNestedTransactionSavePointName()); if ($logger !== null) { $logger->stopQuery(); } } else { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5383', <<<'DEPRECATION' Nesting transactions without enabling savepoints is deprecated. Call %s::setNestTransactionsWithSavepoints(true) to enable savepoints. DEPRECATION, self::class, ); } $eventManager = $this->getEventManager(); if ($eventManager->hasListeners(Events::onTransactionBegin)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onTransactionBegin, ); $eventManager->dispatchEvent(Events::onTransactionBegin, new TransactionBeginEventArgs($this)); } return true; } /** * @return bool * * @throws Exception */ public function commit() { if ($this->transactionNestingLevel === 0) { throw ConnectionException::noActiveTransaction(); } if ($this->isRollbackOnly) { throw ConnectionException::commitFailedRollbackOnly(); } $result = true; $connection = $this->getWrappedConnection(); if ($this->transactionNestingLevel === 1) { $result = $this->doCommit($connection); } elseif ($this->nestTransactionsWithSavepoints) { $this->releaseSavepoint($this->_getNestedTransactionSavePointName()); } --$this->transactionNestingLevel; $eventManager = $this->getEventManager(); if ($eventManager->hasListeners(Events::onTransactionCommit)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onTransactionCommit, ); $eventManager->dispatchEvent(Events::onTransactionCommit, new TransactionCommitEventArgs($this)); } if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) { return $result; } $this->beginTransaction(); return $result; } /** * @return bool * * @throws DriverException */ private function doCommit(DriverConnection $connection) { $logger = $this->_config->getSQLLogger(); if ($logger !== null) { $logger->startQuery('"COMMIT"'); } $result = $connection->commit(); if ($logger !== null) { $logger->stopQuery(); } return $result; } /** * Commits all current nesting transactions. * * @throws Exception */ private function commitAll(): void { while ($this->transactionNestingLevel !== 0) { if ($this->autoCommit === false && $this->transactionNestingLevel === 1) { // When in no auto-commit mode, the last nesting commit immediately starts a new transaction. // Therefore we need to do the final commit here and then leave to avoid an infinite loop. $this->commit(); return; } $this->commit(); } } /** * Cancels any database changes done during the current transaction. * * @return bool * * @throws Exception */ public function rollBack() { if ($this->transactionNestingLevel === 0) { throw ConnectionException::noActiveTransaction(); } $connection = $this->getWrappedConnection(); $logger = $this->_config->getSQLLogger(); if ($this->transactionNestingLevel === 1) { if ($logger !== null) { $logger->startQuery('"ROLLBACK"'); } $this->transactionNestingLevel = 0; $connection->rollBack(); $this->isRollbackOnly = false; if ($logger !== null) { $logger->stopQuery(); } if ($this->autoCommit === false) { $this->beginTransaction(); } } elseif ($this->nestTransactionsWithSavepoints) { if ($logger !== null) { $logger->startQuery('"ROLLBACK TO SAVEPOINT"'); } $this->rollbackSavepoint($this->_getNestedTransactionSavePointName()); --$this->transactionNestingLevel; if ($logger !== null) { $logger->stopQuery(); } } else { $this->isRollbackOnly = true; --$this->transactionNestingLevel; } $eventManager = $this->getEventManager(); if ($eventManager->hasListeners(Events::onTransactionRollBack)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onTransactionRollBack, ); $eventManager->dispatchEvent(Events::onTransactionRollBack, new TransactionRollBackEventArgs($this)); } return true; } /** * Creates a new savepoint. * * @param string $savepoint The name of the savepoint to create. * * @return void * * @throws Exception */ public function createSavepoint($savepoint) { $platform = $this->getDatabasePlatform(); if (! $platform->supportsSavepoints()) { throw ConnectionException::savepointsNotSupported(); } $this->executeStatement($platform->createSavePoint($savepoint)); } /** * Releases the given savepoint. * * @param string $savepoint The name of the savepoint to release. * * @return void * * @throws Exception */ public function releaseSavepoint($savepoint) { $logger = $this->_config->getSQLLogger(); $platform = $this->getDatabasePlatform(); if (! $platform->supportsSavepoints()) { throw ConnectionException::savepointsNotSupported(); } if (! $platform->supportsReleaseSavepoints()) { if ($logger !== null) { $logger->stopQuery(); } return; } if ($logger !== null) { $logger->startQuery('"RELEASE SAVEPOINT"'); } $this->executeStatement($platform->releaseSavePoint($savepoint)); if ($logger === null) { return; } $logger->stopQuery(); } /** * Rolls back to the given savepoint. * * @param string $savepoint The name of the savepoint to rollback to. * * @return void * * @throws Exception */ public function rollbackSavepoint($savepoint) { $platform = $this->getDatabasePlatform(); if (! $platform->supportsSavepoints()) { throw ConnectionException::savepointsNotSupported(); } $this->executeStatement($platform->rollbackSavePoint($savepoint)); } /** * Gets the wrapped driver connection. * * @deprecated Use {@link getNativeConnection()} to access the native connection. * * @return DriverConnection * * @throws Exception */ public function getWrappedConnection() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4966', 'Connection::getWrappedConnection() is deprecated.' . ' Use Connection::getNativeConnection() to access the native connection.', ); $this->connect(); return $this->_conn; } /** @return resource|object */ public function getNativeConnection() { $this->connect(); if (! method_exists($this->_conn, 'getNativeConnection')) { throw new LogicException(sprintf( 'The driver connection %s does not support accessing the native connection.', get_class($this->_conn), )); } return $this->_conn->getNativeConnection(); } /** * Creates a SchemaManager that can be used to inspect or change the * database schema through the connection. * * @throws Exception */ public function createSchemaManager(): AbstractSchemaManager { return $this->schemaManagerFactory->createSchemaManager($this); } /** * Gets the SchemaManager that can be used to inspect or change the * database schema through the connection. * * @deprecated Use {@see createSchemaManager()} instead. * * @return AbstractSchemaManager * * @throws Exception */ public function getSchemaManager() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4515', 'Connection::getSchemaManager() is deprecated, use Connection::createSchemaManager() instead.', ); return $this->_schemaManager ??= $this->createSchemaManager(); } /** * Marks the current transaction so that the only possible * outcome for the transaction to be rolled back. * * @return void * * @throws ConnectionException If no transaction is active. */ public function setRollbackOnly() { if ($this->transactionNestingLevel === 0) { throw ConnectionException::noActiveTransaction(); } $this->isRollbackOnly = true; } /** * Checks whether the current transaction is marked for rollback only. * * @return bool * * @throws ConnectionException If no transaction is active. */ public function isRollbackOnly() { if ($this->transactionNestingLevel === 0) { throw ConnectionException::noActiveTransaction(); } return $this->isRollbackOnly; } /** * Converts a given value to its database representation according to the conversion * rules of a specific DBAL mapping type. * * @param mixed $value The value to convert. * @param string $type The name of the DBAL mapping type. * * @return mixed The converted value. * * @throws Exception */ public function convertToDatabaseValue($value, $type) { return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform()); } /** * Converts a given value to its PHP representation according to the conversion * rules of a specific DBAL mapping type. * * @param mixed $value The value to convert. * @param string $type The name of the DBAL mapping type. * * @return mixed The converted type. * * @throws Exception */ public function convertToPHPValue($value, $type) { return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform()); } /** * Binds a set of parameters, some or all of which are typed with a PDO binding type * or DBAL mapping type, to a given statement. * * @param DriverStatement $stmt Prepared statement * @param list<mixed>|array<string, mixed> $params Statement parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @throws Exception */ private function bindParameters(DriverStatement $stmt, array $params, array $types): void { // Check whether parameters are positional or named. Mixing is not allowed. if (is_int(key($params))) { $bindIndex = 1; foreach ($params as $key => $value) { if (isset($types[$key])) { $type = $types[$key]; [$value, $bindingType] = $this->getBindingInfo($value, $type); } else { if (array_key_exists($key, $types)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5550', 'Using NULL as prepared statement parameter type is deprecated.' . 'Omit or use ParameterType::STRING instead', ); } $bindingType = ParameterType::STRING; } $stmt->bindValue($bindIndex, $value, $bindingType); ++$bindIndex; } } else { // Named parameters foreach ($params as $name => $value) { if (isset($types[$name])) { $type = $types[$name]; [$value, $bindingType] = $this->getBindingInfo($value, $type); } else { if (array_key_exists($name, $types)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5550', 'Using NULL as prepared statement parameter type is deprecated.' . 'Omit or use ParameterType::STRING instead', ); } $bindingType = ParameterType::STRING; } $stmt->bindValue($name, $value, $bindingType); } } } /** * Gets the binding type of a given type. * * @param mixed $value The value to bind. * @param int|string|Type|null $type The type to bind (PDO or DBAL). * * @return array{mixed, int} [0] => the (escaped) value, [1] => the binding type. * * @throws Exception */ private function getBindingInfo($value, $type): array { if (is_string($type)) { $type = Type::getType($type); } if ($type instanceof Type) { $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform()); $bindingType = $type->getBindingType(); } else { $bindingType = $type ?? ParameterType::STRING; } return [$value, $bindingType]; } /** * Creates a new instance of a SQL query builder. * * @return QueryBuilder */ public function createQueryBuilder() { return new Query\QueryBuilder($this); } /** * @internal * * @param list<mixed>|array<string, mixed> $params * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types */ final public function convertExceptionDuringQuery( Driver\Exception $e, string $sql, array $params = [], array $types = [] ): DriverException { return $this->handleDriverException($e, new Query($sql, $params, $types)); } /** @internal */ final public function convertException(Driver\Exception $e): DriverException { return $this->handleDriverException($e, null); } /** * @param array<int, mixed>|array<string, mixed> $params * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types * * @return array{string, list<mixed>, array<int,Type|int|string|null>} */ private function expandArrayParameters(string $sql, array $params, array $types): array { $this->parser ??= $this->getDatabasePlatform()->createSQLParser(); $visitor = new ExpandArrayParameters($params, $types); $this->parser->parse($sql, $visitor); return [ $visitor->getSQL(), $visitor->getParameters(), $visitor->getTypes(), ]; } /** * @param array<int, mixed>|array<string, mixed> $params * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types */ private function needsArrayParameterConversion(array $params, array $types): bool { if (is_string(key($params))) { return true; } foreach ($types as $type) { if ( $type === ArrayParameterType::INTEGER || $type === ArrayParameterType::STRING || $type === ArrayParameterType::ASCII || $type === ArrayParameterType::BINARY ) { return true; } } return false; } private function handleDriverException( Driver\Exception $driverException, ?Query $query ): DriverException { $this->exceptionConverter ??= $this->_driver->getExceptionConverter(); $exception = $this->exceptionConverter->convert($driverException, $query); if ($exception instanceof ConnectionLost) { $this->close(); } return $exception; } /** * BC layer for a wide-spread use-case of old DBAL APIs * * @deprecated Use {@see executeStatement()} instead * * @param array<mixed> $params The query parameters * @param array<int|string|null> $types The parameter types */ public function executeUpdate(string $sql, array $params = [], array $types = []): int { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4163', '%s is deprecated, please use executeStatement() instead.', __METHOD__, ); return $this->executeStatement($sql, $params, $types); } /** * BC layer for a wide-spread use-case of old DBAL APIs * * @deprecated Use {@see executeQuery()} instead */ public function query(string $sql): Result { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4163', '%s is deprecated, please use executeQuery() instead.', __METHOD__, ); return $this->executeQuery($sql); } /** * BC layer for a wide-spread use-case of old DBAL APIs * * @deprecated please use {@see executeStatement()} instead */ public function exec(string $sql): int { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4163', '%s is deprecated, please use executeStatement() instead.', __METHOD__, ); return $this->executeStatement($sql); } } dbal/src/Logging/Connection.php 0000644 00000003437 15021222234 0012433 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Logging; use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Psr\Log\LoggerInterface; final class Connection extends AbstractConnectionMiddleware { private LoggerInterface $logger; /** @internal This connection can be only instantiated by its driver. */ public function __construct(ConnectionInterface $connection, LoggerInterface $logger) { parent::__construct($connection); $this->logger = $logger; } public function __destruct() { $this->logger->info('Disconnecting'); } public function prepare(string $sql): DriverStatement { return new Statement( parent::prepare($sql), $this->logger, $sql, ); } public function query(string $sql): Result { $this->logger->debug('Executing query: {sql}', ['sql' => $sql]); return parent::query($sql); } public function exec(string $sql): int { $this->logger->debug('Executing statement: {sql}', ['sql' => $sql]); return parent::exec($sql); } /** * {@inheritDoc} */ public function beginTransaction() { $this->logger->debug('Beginning transaction'); return parent::beginTransaction(); } /** * {@inheritDoc} */ public function commit() { $this->logger->debug('Committing transaction'); return parent::commit(); } /** * {@inheritDoc} */ public function rollBack() { $this->logger->debug('Rolling back transaction'); return parent::rollBack(); } } dbal/src/Logging/DebugStack.php 0000644 00000002644 15021222234 0012347 0 ustar 00 <?php namespace Doctrine\DBAL\Logging; use Doctrine\Deprecations\Deprecation; use function microtime; /** * Includes executed SQLs in a Debug Stack. * * @deprecated */ class DebugStack implements SQLLogger { /** * Executed SQL queries. * * @var array<int, array<string, mixed>> */ public $queries = []; /** * If Debug Stack is enabled (log queries) or not. * * @var bool */ public $enabled = true; /** @var float|null */ public $start = null; /** @var int */ public $currentQuery = 0; public function __construct() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4967', 'DebugStack is deprecated.', ); } /** * {@inheritDoc} */ public function startQuery($sql, ?array $params = null, ?array $types = null) { if (! $this->enabled) { return; } $this->start = microtime(true); $this->queries[++$this->currentQuery] = [ 'sql' => $sql, 'params' => $params, 'types' => $types, 'executionMS' => 0, ]; } /** * {@inheritDoc} */ public function stopQuery() { if (! $this->enabled) { return; } $this->queries[$this->currentQuery]['executionMS'] = microtime(true) - $this->start; } } dbal/src/Logging/Driver.php 0000644 00000002525 15021222234 0011564 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Logging; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use Psr\Log\LoggerInterface; use SensitiveParameter; final class Driver extends AbstractDriverMiddleware { private LoggerInterface $logger; /** @internal This driver can be only instantiated by its middleware. */ public function __construct(DriverInterface $driver, LoggerInterface $logger) { parent::__construct($driver); $this->logger = $logger; } /** * {@inheritDoc} */ public function connect( #[SensitiveParameter] array $params ) { $this->logger->info('Connecting with parameters {params}', ['params' => $this->maskPassword($params)]); return new Connection( parent::connect($params), $this->logger, ); } /** * @param array<string,mixed> $params Connection parameters * * @return array<string,mixed> */ private function maskPassword( #[SensitiveParameter] array $params ): array { if (isset($params['password'])) { $params['password'] = '<redacted>'; } if (isset($params['url'])) { $params['url'] = '<redacted>'; } return $params; } } dbal/src/Logging/LoggerChain.php 0000644 00000001730 15021222234 0012510 0 ustar 00 <?php namespace Doctrine\DBAL\Logging; use Doctrine\Deprecations\Deprecation; /** * Chains multiple SQLLogger. * * @deprecated */ class LoggerChain implements SQLLogger { /** @var iterable<SQLLogger> */ private iterable $loggers; /** @param iterable<SQLLogger> $loggers */ public function __construct(iterable $loggers = []) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4967', 'LoggerChain is deprecated', ); $this->loggers = $loggers; } /** * {@inheritDoc} */ public function startQuery($sql, ?array $params = null, ?array $types = null) { foreach ($this->loggers as $logger) { $logger->startQuery($sql, $params, $types); } } /** * {@inheritDoc} */ public function stopQuery() { foreach ($this->loggers as $logger) { $logger->stopQuery(); } } } dbal/src/Logging/Middleware.php 0000644 00000001023 15021222234 0012376 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Logging; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface; use Psr\Log\LoggerInterface; final class Middleware implements MiddlewareInterface { private LoggerInterface $logger; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } public function wrap(DriverInterface $driver): DriverInterface { return new Driver($driver, $this->logger); } } dbal/src/Logging/SQLLogger.php 0000644 00000001665 15021222234 0012134 0 ustar 00 <?php namespace Doctrine\DBAL\Logging; use Doctrine\DBAL\Types\Type; /** * Interface for SQL loggers. * * @deprecated Use {@see \Doctrine\DBAL\Logging\Middleware} or implement * {@see \Doctrine\DBAL\Driver\Middleware} instead. */ interface SQLLogger { /** * Logs a SQL statement somewhere. * * @param string $sql SQL statement * @param list<mixed>|array<string, mixed>|null $params Statement parameters * @param array<int, Type|int|string|null>|array<string, Type|int|string|null>|null $types Parameter types * * @return void */ public function startQuery($sql, ?array $params = null, ?array $types = null); /** * Marks the last started query as stopped. This can be used for timing of queries. * * @return void */ public function stopQuery(); } dbal/src/Logging/Statement.php 0000644 00000005630 15021222234 0012275 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Logging; use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use Psr\Log\LoggerInterface; use function array_slice; use function func_get_args; use function func_num_args; final class Statement extends AbstractStatementMiddleware { private LoggerInterface $logger; private string $sql; /** @var array<int,mixed>|array<string,mixed> */ private array $params = []; /** @var array<int,int>|array<string,int> */ private array $types = []; /** @internal This statement can be only instantiated by its connection. */ public function __construct(StatementInterface $statement, LoggerInterface $logger, string $sql) { parent::__construct($statement); $this->logger = $logger; $this->sql = $sql; } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } $this->params[$param] = &$variable; $this->types[$param] = $type; return parent::bindParam($param, $variable, $type, ...array_slice(func_get_args(), 3)); } /** * {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING) { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } $this->params[$param] = $value; $this->types[$param] = $type; return parent::bindValue($param, $value, $type); } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { $this->logger->debug('Executing statement: {sql} (parameters: {params}, types: {types})', [ 'sql' => $this->sql, 'params' => $params ?? $this->params, 'types' => $this->types, ]); return parent::execute($params); } } dbal/src/DriverManager.php 0000644 00000022221 15021222234 0011464 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\Common\EventManager; use Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Exception\MalformedDsnException; use Doctrine\DBAL\Tools\DsnParser; use Doctrine\Deprecations\Deprecation; use SensitiveParameter; use function array_keys; use function array_merge; use function is_a; /** * Factory for creating {@see Connection} instances. * * @psalm-type OverrideParams = array{ * application_name?: string, * charset?: string, * dbname?: string, * default_dbname?: string, * driver?: key-of<self::DRIVER_MAP>, * driverClass?: class-string<Driver>, * driverOptions?: array<mixed>, * host?: string, * password?: string, * path?: string, * persistent?: bool, * platform?: Platforms\AbstractPlatform, * port?: int, * serverVersion?: string, * url?: string, * user?: string, * unix_socket?: string, * } * @psalm-type Params = array{ * application_name?: string, * charset?: string, * dbname?: string, * defaultTableOptions?: array<string, mixed>, * default_dbname?: string, * driver?: key-of<self::DRIVER_MAP>, * driverClass?: class-string<Driver>, * driverOptions?: array<mixed>, * host?: string, * keepSlave?: bool, * keepReplica?: bool, * master?: OverrideParams, * memory?: bool, * password?: string, * path?: string, * persistent?: bool, * platform?: Platforms\AbstractPlatform, * port?: int, * primary?: OverrideParams, * replica?: array<OverrideParams>, * serverVersion?: string, * sharding?: array<string,mixed>, * slaves?: array<OverrideParams>, * url?: string, * user?: string, * wrapperClass?: class-string<Connection>, * unix_socket?: string, * } */ final class DriverManager { /** * List of supported drivers and their mappings to the driver classes. * * To add your own driver use the 'driverClass' parameter to {@see DriverManager::getConnection()}. */ private const DRIVER_MAP = [ 'pdo_mysql' => PDO\MySQL\Driver::class, 'pdo_sqlite' => PDO\SQLite\Driver::class, 'pdo_pgsql' => PDO\PgSQL\Driver::class, 'pdo_oci' => PDO\OCI\Driver::class, 'oci8' => OCI8\Driver::class, 'ibm_db2' => IBMDB2\Driver::class, 'pdo_sqlsrv' => PDO\SQLSrv\Driver::class, 'mysqli' => Mysqli\Driver::class, 'pgsql' => PgSQL\Driver::class, 'sqlsrv' => SQLSrv\Driver::class, 'sqlite3' => SQLite3\Driver::class, ]; /** * List of URL schemes from a database URL and their mappings to driver. * * @deprecated Use actual driver names instead. * * @var array<string, string> * @psalm-var array<string, key-of<self::DRIVER_MAP>> */ private static array $driverSchemeAliases = [ 'db2' => 'ibm_db2', 'mssql' => 'pdo_sqlsrv', 'mysql' => 'pdo_mysql', 'mysql2' => 'pdo_mysql', // Amazon RDS, for some weird reason 'postgres' => 'pdo_pgsql', 'postgresql' => 'pdo_pgsql', 'pgsql' => 'pdo_pgsql', 'sqlite' => 'pdo_sqlite', 'sqlite3' => 'pdo_sqlite', ]; /** * Private constructor. This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } /** * Creates a connection object based on the specified parameters. * This method returns a Doctrine\DBAL\Connection which wraps the underlying * driver connection. * * $params must contain at least one of the following. * * Either 'driver' with one of the array keys of {@see DRIVER_MAP}, * OR 'driverClass' that contains the full class name (with namespace) of the * driver class to instantiate. * * Other (optional) parameters: * * <b>user (string)</b>: * The username to use when connecting. * * <b>password (string)</b>: * The password to use when connecting. * * <b>driverOptions (array)</b>: * Any additional driver-specific options for the driver. These are just passed * through to the driver. * * <b>wrapperClass</b>: * You may specify a custom wrapper class through the 'wrapperClass' * parameter but this class MUST inherit from Doctrine\DBAL\Connection. * * <b>driverClass</b>: * The driver class to use. * * @param Configuration|null $config The configuration to use. * @param EventManager|null $eventManager The event manager to use. * @psalm-param Params $params * * @psalm-return ($params is array{wrapperClass: class-string<T>} ? T : Connection) * * @throws Exception * * @template T of Connection */ public static function getConnection( #[SensitiveParameter] array $params, ?Configuration $config = null, ?EventManager $eventManager = null ): Connection { // create default config and event manager, if not set $config ??= new Configuration(); $eventManager ??= new EventManager(); $params = self::parseDatabaseUrl($params); // URL support for PrimaryReplicaConnection if (isset($params['primary'])) { $params['primary'] = self::parseDatabaseUrl($params['primary']); } if (isset($params['replica'])) { foreach ($params['replica'] as $key => $replicaParams) { $params['replica'][$key] = self::parseDatabaseUrl($replicaParams); } } $driver = self::createDriver($params['driver'] ?? null, $params['driverClass'] ?? null); foreach ($config->getMiddlewares() as $middleware) { $driver = $middleware->wrap($driver); } $wrapperClass = $params['wrapperClass'] ?? Connection::class; if (! is_a($wrapperClass, Connection::class, true)) { throw Exception::invalidWrapperClass($wrapperClass); } return new $wrapperClass($params, $driver, $config, $eventManager); } /** * Returns the list of supported drivers. * * @return string[] * @psalm-return list<key-of<self::DRIVER_MAP>> */ public static function getAvailableDrivers(): array { return array_keys(self::DRIVER_MAP); } /** * @throws Exception * * @psalm-assert key-of<self::DRIVER_MAP>|null $driver * @psalm-assert class-string<Driver>|null $driverClass */ private static function createDriver(?string $driver, ?string $driverClass): Driver { if ($driverClass === null) { if ($driver === null) { throw Exception::driverRequired(); } if (! isset(self::DRIVER_MAP[$driver])) { throw Exception::unknownDriver($driver, array_keys(self::DRIVER_MAP)); } $driverClass = self::DRIVER_MAP[$driver]; } elseif (! is_a($driverClass, Driver::class, true)) { throw Exception::invalidDriverClass($driverClass); } return new $driverClass(); } /** * Extracts parts from a database URL, if present, and returns an * updated list of parameters. * * @param mixed[] $params The list of parameters. * @psalm-param Params $params * * @return mixed[] A modified list of parameters with info from a database * URL extracted into indidivual parameter parts. * @psalm-return Params * * @throws Exception */ private static function parseDatabaseUrl( #[SensitiveParameter] array $params ): array { if (! isset($params['url'])) { return $params; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5843', 'The "url" connection parameter is deprecated. Please use %s to parse a database url before calling %s.', DsnParser::class, self::class, ); $parser = new DsnParser(self::$driverSchemeAliases); try { $parsedParams = $parser->parse($params['url']); } catch (MalformedDsnException $e) { throw new Exception('Malformed parameter "url".', 0, $e); } if (isset($parsedParams['driver'])) { // The requested driver from the URL scheme takes precedence // over the default custom driver from the connection parameters (if any). unset($params['driverClass']); } $params = array_merge($params, $parsedParams); // If a schemeless connection URL is given, we require a default driver or default custom driver // as connection parameter. if (! isset($params['driverClass']) && ! isset($params['driver'])) { throw Exception::driverRequired($params['url']); } return $params; } } dbal/src/Driver/AbstractDB2Driver.php 0000644 00000005671 15021222234 0013412 0 ustar 00 <?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']; } } dbal/src/Driver/PDO/PDOException.php 0000644 00000001307 15021222234 0013116 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\Exception as DriverException; /** * @internal * * @psalm-immutable */ final class PDOException extends \PDOException implements DriverException { private ?string $sqlState = null; public static function new(\PDOException $previous): self { $exception = new self($previous->message, 0, $previous); $exception->errorInfo = $previous->errorInfo; $exception->code = $previous->code; $exception->sqlState = $previous->errorInfo[0] ?? null; return $exception; } public function getSQLState(): ?string { return $this->sqlState; } } dbal/src/Driver/PDO/SQLSrv/Connection.php 0000644 00000003553 15021222234 0014053 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLSrv; use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\Deprecations\Deprecation; use PDO; final class Connection extends AbstractConnectionMiddleware { private PDOConnection $connection; public function __construct(PDOConnection $connection) { parent::__construct($connection); $this->connection = $connection; } public function prepare(string $sql): StatementInterface { return new Statement( $this->connection->prepare($sql), ); } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name === null) { return parent::lastInsertId($name); } Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); $statement = $this->prepare( 'SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?', ); $statement->bindValue(1, $name); return $statement->execute() ->fetchOne(); } public function getNativeConnection(): PDO { return $this->connection->getNativeConnection(); } /** @deprecated Call {@see getNativeConnection()} instead. */ public function getWrappedConnection(): PDO { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5037', '%s is deprecated, call getNativeConnection() instead.', __METHOD__, ); return $this->connection->getWrappedConnection(); } } dbal/src/Driver/PDO/SQLSrv/Driver.php 0000644 00000005660 15021222234 0013210 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLSrv; use Doctrine\DBAL\Driver\AbstractSQLServerDriver; use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection; use Doctrine\DBAL\Driver\PDO\Exception as PDOException; use PDO; use SensitiveParameter; use function is_int; use function sprintf; final class Driver extends AbstractSQLServerDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $driverOptions = $dsnOptions = []; if (isset($params['driverOptions'])) { foreach ($params['driverOptions'] as $option => $value) { if (is_int($option)) { $driverOptions[$option] = $value; } else { $dsnOptions[$option] = $value; } } } if (! empty($params['persistent'])) { $driverOptions[PDO::ATTR_PERSISTENT] = true; } $safeParams = $params; unset($safeParams['password'], $safeParams['url']); try { $pdo = new PDO( $this->constructDsn($safeParams, $dsnOptions), $params['user'] ?? '', $params['password'] ?? '', $driverOptions, ); } catch (\PDOException $exception) { throw PDOException::new($exception); } return new Connection(new PDOConnection($pdo)); } /** * Constructs the Sqlsrv PDO DSN. * * @param mixed[] $params * @param string[] $connectionOptions * * @throws Exception */ private function constructDsn(array $params, array $connectionOptions): string { $dsn = 'sqlsrv:server='; if (isset($params['host'])) { $dsn .= $params['host']; if (isset($params['port'])) { $dsn .= ',' . $params['port']; } } elseif (isset($params['port'])) { throw PortWithoutHost::new(); } if (isset($params['dbname'])) { $connectionOptions['Database'] = $params['dbname']; } if (isset($params['MultipleActiveResultSets'])) { $connectionOptions['MultipleActiveResultSets'] = $params['MultipleActiveResultSets'] ? 'true' : 'false'; } return $dsn . $this->getConnectionOptionsDsn($connectionOptions); } /** * Converts a connection options array to the DSN * * @param string[] $connectionOptions */ private function getConnectionOptionsDsn(array $connectionOptions): string { $connectionOptionsDsn = ''; foreach ($connectionOptions as $paramName => $paramValue) { $connectionOptionsDsn .= sprintf(';%s=%s', $paramName, $paramValue); } return $connectionOptionsDsn; } } dbal/src/Driver/PDO/SQLSrv/Statement.php 0000644 00000006327 15021222234 0013722 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLSrv; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; use Doctrine\DBAL\Driver\PDO\Statement as PDOStatement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use PDO; use function func_num_args; final class Statement extends AbstractStatementMiddleware { private PDOStatement $statement; /** @internal The statement can be only instantiated by its driver connection. */ public function __construct(PDOStatement $statement) { parent::__construct($statement); $this->statement = $statement; } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. * * @param string|int $param * @param mixed $variable * @param int $type * @param int|null $length * @param mixed $driverOptions The usage of the argument is deprecated. * * @throws UnknownParameterType * * @psalm-assert ParameterType::* $type */ public function bindParam( $param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null ): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (func_num_args() > 4) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4533', 'The $driverOptions argument of Statement::bindParam() is deprecated.', ); } switch ($type) { case ParameterType::LARGE_OBJECT: case ParameterType::BINARY: $driverOptions ??= PDO::SQLSRV_ENCODING_BINARY; break; case ParameterType::ASCII: $type = ParameterType::STRING; $length = 0; $driverOptions = PDO::SQLSRV_ENCODING_SYSTEM; break; } return $this->statement->bindParam($param, $variable, $type, $length ?? 0, $driverOptions); } /** * @throws UnknownParameterType * * {@inheritDoc} * * @psalm-assert ParameterType::* $type */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->bindParam($param, $value, $type); } } dbal/src/Driver/PDO/ParameterTypeMap.php 0000644 00000002173 15021222234 0014037 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\ParameterType; use PDO; /** @internal */ final class ParameterTypeMap { private const PARAM_TYPE_MAP = [ ParameterType::NULL => PDO::PARAM_NULL, ParameterType::INTEGER => PDO::PARAM_INT, ParameterType::STRING => PDO::PARAM_STR, ParameterType::ASCII => PDO::PARAM_STR, ParameterType::BINARY => PDO::PARAM_LOB, ParameterType::LARGE_OBJECT => PDO::PARAM_LOB, ParameterType::BOOLEAN => PDO::PARAM_BOOL, ]; /** * Converts DBAL parameter type to PDO parameter type * * @psalm-return PDO::PARAM_* * * @throws UnknownParameterType * * @psalm-assert ParameterType::* $type */ public static function convertParamType(int $type): int { if (! isset(self::PARAM_TYPE_MAP[$type])) { throw UnknownParameterType::new($type); } return self::PARAM_TYPE_MAP[$type]; } private function __construct() { } private function __clone() { } } dbal/src/Driver/PDO/Connection.php 0000644 00000010042 15021222234 0012710 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\Driver\PDO\PDOException as DriverPDOException; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use PDO; use PDOException; use PDOStatement; use function assert; final class Connection implements ServerInfoAwareConnection { private PDO $connection; /** @internal The connection can be only instantiated by its driver. */ public function __construct(PDO $connection) { $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->connection = $connection; } public function exec(string $sql): int { try { $result = $this->connection->exec($sql); assert($result !== false); return $result; } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritDoc} */ public function getServerVersion() { return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); } /** * {@inheritDoc} * * @return Statement */ public function prepare(string $sql): StatementInterface { try { $stmt = $this->connection->prepare($sql); assert($stmt instanceof PDOStatement); return new Statement($stmt); } catch (PDOException $exception) { throw Exception::new($exception); } } public function query(string $sql): ResultInterface { try { $stmt = $this->connection->query($sql); assert($stmt instanceof PDOStatement); return new Result($stmt); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritDoc} * * @throws UnknownParameterType * * @psalm-assert ParameterType::* $type */ public function quote($value, $type = ParameterType::STRING) { return $this->connection->quote($value, ParameterTypeMap::convertParamType($type)); } /** * {@inheritDoc} */ public function lastInsertId($name = null) { try { if ($name === null) { return $this->connection->lastInsertId(); } Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); return $this->connection->lastInsertId($name); } catch (PDOException $exception) { throw Exception::new($exception); } } public function beginTransaction(): bool { try { return $this->connection->beginTransaction(); } catch (PDOException $exception) { throw DriverPDOException::new($exception); } } public function commit(): bool { try { return $this->connection->commit(); } catch (PDOException $exception) { throw DriverPDOException::new($exception); } } public function rollBack(): bool { try { return $this->connection->rollBack(); } catch (PDOException $exception) { throw DriverPDOException::new($exception); } } public function getNativeConnection(): PDO { return $this->connection; } /** @deprecated Call {@see getNativeConnection()} instead. */ public function getWrappedConnection(): PDO { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5037', '%s is deprecated, call getNativeConnection() instead.', __METHOD__, ); return $this->getNativeConnection(); } } dbal/src/Driver/PDO/Exception.php 0000644 00000001151 15021222234 0012550 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\AbstractException; use PDOException; /** * @internal * * @psalm-immutable */ final class Exception extends AbstractException { public static function new(PDOException $exception): self { if ($exception->errorInfo !== null) { [$sqlState, $code] = $exception->errorInfo; $code ??= 0; } else { $code = $exception->getCode(); $sqlState = null; } return new self($exception->getMessage(), $sqlState, $code, $exception); } } dbal/src/Driver/PDO/Result.php 0000644 00000004674 15021222234 0012105 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\Result as ResultInterface; use PDO; use PDOException; use PDOStatement; final class Result implements ResultInterface { private PDOStatement $statement; /** @internal The result can be only instantiated by its driver connection or statement. */ public function __construct(PDOStatement $statement) { $this->statement = $statement; } /** * {@inheritDoc} */ public function fetchNumeric() { return $this->fetch(PDO::FETCH_NUM); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->fetch(PDO::FETCH_ASSOC); } /** * {@inheritDoc} */ public function fetchOne() { return $this->fetch(PDO::FETCH_COLUMN); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return $this->fetchAll(PDO::FETCH_NUM); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return $this->fetchAll(PDO::FETCH_ASSOC); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return $this->fetchAll(PDO::FETCH_COLUMN); } public function rowCount(): int { try { return $this->statement->rowCount(); } catch (PDOException $exception) { throw Exception::new($exception); } } public function columnCount(): int { try { return $this->statement->columnCount(); } catch (PDOException $exception) { throw Exception::new($exception); } } public function free(): void { $this->statement->closeCursor(); } /** * @psalm-param PDO::FETCH_* $mode * * @return mixed * * @throws Exception */ private function fetch(int $mode) { try { return $this->statement->fetch($mode); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * @psalm-param PDO::FETCH_* $mode * * @return list<mixed> * * @throws Exception */ private function fetchAll(int $mode): array { try { return $this->statement->fetchAll($mode); } catch (PDOException $exception) { throw Exception::new($exception); } } } dbal/src/Driver/PDO/MySQL/Driver.php 0000644 00000003474 15021222234 0013024 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\MySQL; use Doctrine\DBAL\Driver\AbstractMySQLDriver; use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDO\Exception; use PDO; use PDOException; use SensitiveParameter; final class Driver extends AbstractMySQLDriver { /** * {@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); } return new Connection($pdo); } /** * Constructs the MySQL PDO DSN. * * @param mixed[] $params */ private function constructPdoDsn(array $params): string { $dsn = 'mysql:'; if (isset($params['host']) && $params['host'] !== '') { $dsn .= 'host=' . $params['host'] . ';'; } if (isset($params['port'])) { $dsn .= 'port=' . $params['port'] . ';'; } if (isset($params['dbname'])) { $dsn .= 'dbname=' . $params['dbname'] . ';'; } if (isset($params['unix_socket'])) { $dsn .= 'unix_socket=' . $params['unix_socket'] . ';'; } if (isset($params['charset'])) { $dsn .= 'charset=' . $params['charset'] . ';'; } return $dsn; } } dbal/src/Driver/PDO/OCI/Driver.php 0000644 00000002624 15021222234 0012465 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\OCI; use Doctrine\DBAL\Driver\AbstractOracleDriver; use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDO\Exception; use PDO; use PDOException; use SensitiveParameter; final class Driver extends AbstractOracleDriver { /** * {@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($params), $params['user'] ?? '', $params['password'] ?? '', $driverOptions, ); } catch (PDOException $exception) { throw Exception::new($exception); } return new Connection($pdo); } /** * Constructs the Oracle PDO DSN. * * @param mixed[] $params */ private function constructPdoDsn(array $params): string { $dsn = 'oci:dbname=' . $this->getEasyConnectString($params); if (isset($params['charset'])) { $dsn .= ';charset=' . $params['charset']; } return $dsn; } } dbal/src/Driver/PDO/SQLite/Driver.php 0000644 00000004140 15021222234 0013207 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLite; use Doctrine\DBAL\Driver\AbstractSQLiteDriver; use Doctrine\DBAL\Driver\API\SQLite\UserDefinedFunctions; use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\Deprecations\Deprecation; use PDO; use PDOException; use SensitiveParameter; use function array_intersect_key; final class Driver extends AbstractSQLiteDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $driverOptions = $params['driverOptions'] ?? []; $userDefinedFunctions = []; if (isset($driverOptions['userDefinedFunctions'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5742', 'The SQLite-specific driver option "userDefinedFunctions" is deprecated.' . ' Register function directly on the native connection instead.', ); $userDefinedFunctions = $driverOptions['userDefinedFunctions']; unset($driverOptions['userDefinedFunctions']); } try { $pdo = new PDO( $this->constructPdoDsn(array_intersect_key($params, ['path' => true, 'memory' => true])), $params['user'] ?? '', $params['password'] ?? '', $driverOptions, ); } catch (PDOException $exception) { throw Exception::new($exception); } UserDefinedFunctions::register( [$pdo, 'sqliteCreateFunction'], $userDefinedFunctions, ); return new Connection($pdo); } /** * Constructs the Sqlite PDO DSN. * * @param array<string, mixed> $params */ private function constructPdoDsn(array $params): string { $dsn = 'sqlite:'; if (isset($params['path'])) { $dsn .= $params['path']; } elseif (isset($params['memory'])) { $dsn .= ':memory:'; } return $dsn; } } dbal/src/Driver/PDO/PgSQL/Driver.php 0000644 00000010151 15021222234 0012773 0 ustar 00 <?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; } } dbal/src/Driver/PDO/Statement.php 0000644 00000007636 15021222234 0012574 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use PDOException; use PDOStatement; use function array_slice; use function func_get_args; use function func_num_args; final class Statement implements StatementInterface { private PDOStatement $stmt; /** @internal The statement can be only instantiated by its driver connection. */ public function __construct(PDOStatement $stmt) { $this->stmt = $stmt; } /** * {@inheritDoc} * * @throws UnknownParameterType * * @psalm-assert ParameterType::* $type */ public function bindValue($param, $value, $type = ParameterType::STRING) { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } $pdoType = ParameterTypeMap::convertParamType($type); try { return $this->stmt->bindValue($param, $value, $pdoType); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. * * @param mixed $param * @param mixed $variable * @param int $type * @param int|null $length * @param mixed $driverOptions The usage of the argument is deprecated. * * @throws UnknownParameterType * * @psalm-assert ParameterType::* $type */ public function bindParam( $param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null ): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (func_num_args() > 4) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4533', 'The $driverOptions argument of Statement::bindParam() is deprecated.', ); } $pdoType = ParameterTypeMap::convertParamType($type); try { return $this->stmt->bindParam( $param, $variable, $pdoType, $length ?? 0, ...array_slice(func_get_args(), 4), ); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); } try { $this->stmt->execute($params); } catch (PDOException $exception) { throw Exception::new($exception); } return new Result($this->stmt); } } dbal/src/Driver/SQLSrv/Connection.php 0000644 00000006450 15021222234 0013430 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\SQLSrv\Exception\Error; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function is_float; use function is_int; use function sprintf; use function sqlsrv_begin_transaction; use function sqlsrv_commit; use function sqlsrv_query; use function sqlsrv_rollback; use function sqlsrv_rows_affected; use function sqlsrv_server_info; use function str_replace; final class Connection implements ServerInfoAwareConnection { /** @var resource */ private $connection; /** * @internal The connection can be only instantiated by its driver. * * @param resource $connection */ public function __construct($connection) { $this->connection = $connection; } /** * {@inheritDoc} */ public function getServerVersion() { $serverInfo = sqlsrv_server_info($this->connection); return $serverInfo['SQLServerVersion']; } public function prepare(string $sql): DriverStatement { return new Statement($this->connection, $sql); } public function query(string $sql): ResultInterface { return $this->prepare($sql)->execute(); } /** * {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { if (is_int($value)) { return $value; } if (is_float($value)) { return sprintf('%F', $value); } return "'" . str_replace("'", "''", $value) . "'"; } public function exec(string $sql): int { $stmt = sqlsrv_query($this->connection, $sql); if ($stmt === false) { throw Error::new(); } $rowsAffected = sqlsrv_rows_affected($stmt); if ($rowsAffected === false) { throw Error::new(); } return $rowsAffected; } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name !== null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); $result = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?') ->execute([$name]); } else { $result = $this->query('SELECT @@IDENTITY'); } return $result->fetchOne(); } public function beginTransaction(): bool { if (! sqlsrv_begin_transaction($this->connection)) { throw Error::new(); } return true; } public function commit(): bool { if (! sqlsrv_commit($this->connection)) { throw Error::new(); } return true; } public function rollBack(): bool { if (! sqlsrv_rollback($this->connection)) { throw Error::new(); } return true; } /** @return resource */ public function getNativeConnection() { return $this->connection; } } dbal/src/Driver/SQLSrv/Driver.php 0000644 00000003414 15021222234 0012561 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\AbstractSQLServerDriver; use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost; use Doctrine\DBAL\Driver\SQLSrv\Exception\Error; use SensitiveParameter; use function sqlsrv_configure; use function sqlsrv_connect; /** * Driver for ext/sqlsrv. */ final class Driver extends AbstractSQLServerDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $serverName = ''; if (isset($params['host'])) { $serverName = $params['host']; if (isset($params['port'])) { $serverName .= ',' . $params['port']; } } elseif (isset($params['port'])) { throw PortWithoutHost::new(); } $driverOptions = $params['driverOptions'] ?? []; if (isset($params['dbname'])) { $driverOptions['Database'] = $params['dbname']; } if (isset($params['charset'])) { $driverOptions['CharacterSet'] = $params['charset']; } if (isset($params['user'])) { $driverOptions['UID'] = $params['user']; } if (isset($params['password'])) { $driverOptions['PWD'] = $params['password']; } if (! isset($driverOptions['ReturnDatesAsStrings'])) { $driverOptions['ReturnDatesAsStrings'] = 1; } if (! sqlsrv_configure('WarningsReturnAsErrors', 0)) { throw Error::new(); } $connection = sqlsrv_connect($serverName, $driverOptions); if ($connection === false) { throw Error::new(); } return new Connection($connection); } } dbal/src/Driver/SQLSrv/Exception/Error.php 0000644 00000001727 15021222234 0014362 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\SQLSrv\Exception; use Doctrine\DBAL\Driver\AbstractException; use function rtrim; use function sqlsrv_errors; use const SQLSRV_ERR_ERRORS; /** * @internal * * @psalm-immutable */ final class Error extends AbstractException { public static function new(): self { $message = ''; $sqlState = null; $code = 0; foreach ((array) sqlsrv_errors(SQLSRV_ERR_ERRORS) as $error) { $message .= 'SQLSTATE [' . $error['SQLSTATE'] . ', ' . $error['code'] . ']: ' . $error['message'] . "\n"; $sqlState ??= $error['SQLSTATE']; if ($code !== 0) { continue; } $code = $error['code']; } if ($message === '') { $message = 'SQL Server error occurred but no error message was retrieved from driver.'; } return new self(rtrim($message), $sqlState, $code); } } dbal/src/Driver/SQLSrv/Result.php 0000644 00000004760 15021222234 0012611 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result as ResultInterface; use function sqlsrv_fetch; use function sqlsrv_fetch_array; use function sqlsrv_num_fields; use function sqlsrv_rows_affected; use const SQLSRV_FETCH_ASSOC; use const SQLSRV_FETCH_NUMERIC; final class Result implements ResultInterface { /** @var resource */ private $statement; /** * @internal The result can be only instantiated by its driver connection or statement. * * @param resource $stmt */ public function __construct($stmt) { $this->statement = $stmt; } /** * {@inheritDoc} */ public function fetchNumeric() { return $this->fetch(SQLSRV_FETCH_NUMERIC); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->fetch(SQLSRV_FETCH_ASSOC); } /** * {@inheritDoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } public function rowCount(): int { $count = sqlsrv_rows_affected($this->statement); if ($count !== false) { return $count; } return 0; } public function columnCount(): int { $count = sqlsrv_num_fields($this->statement); if ($count !== false) { return $count; } return 0; } public function free(): void { // emulate it by fetching and discarding rows, similarly to what PDO does in this case // @link http://php.net/manual/en/pdostatement.closecursor.php // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075 // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them while (sqlsrv_fetch($this->statement)) { } } /** @return mixed|false */ private function fetch(int $fetchType) { return sqlsrv_fetch_array($this->statement, $fetchType) ?? false; } } dbal/src/Driver/SQLSrv/Statement.php 0000644 00000013510 15021222234 0013270 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\SQLSrv\Exception\Error; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function assert; use function func_num_args; use function is_int; use function sqlsrv_execute; use function SQLSRV_PHPTYPE_STREAM; use function SQLSRV_PHPTYPE_STRING; use function sqlsrv_prepare; use function SQLSRV_SQLTYPE_VARBINARY; use function stripos; use const SQLSRV_ENC_BINARY; use const SQLSRV_ENC_CHAR; use const SQLSRV_PARAM_IN; final class Statement implements StatementInterface { /** * The SQLSRV Resource. * * @var resource */ private $conn; /** * The SQL statement to execute. */ private string $sql; /** * The SQLSRV statement resource. * * @var resource|null */ private $stmt; /** * References to the variables bound as statement parameters. * * @var array<int, mixed> */ private array $variables = []; /** * Bound parameter types. * * @var array<int, int> */ private array $types = []; /** * Append to any INSERT query to retrieve the last insert id. */ private const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;'; /** * @internal The statement can be only instantiated by its driver connection. * * @param resource $conn * @param string $sql */ public function __construct($conn, $sql) { $this->conn = $conn; $this->sql = $sql; if (stripos($sql, 'INSERT INTO ') !== 0) { return; } $this->sql .= self::LAST_INSERT_ID_SQL; } /** * {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } $this->variables[$param] = $value; $this->types[$param] = $type; return true; } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } $this->variables[$param] =& $variable; $this->types[$param] = $type; // unset the statement resource if it exists as the new one will need to be bound to the new variable $this->stmt = null; return true; } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); foreach ($params as $key => $val) { if (is_int($key)) { $this->bindValue($key + 1, $val, ParameterType::STRING); } else { $this->bindValue($key, $val, ParameterType::STRING); } } } $this->stmt ??= $this->prepare(); if (! sqlsrv_execute($this->stmt)) { throw Error::new(); } return new Result($this->stmt); } /** * Prepares SQL Server statement resource * * @return resource * * @throws Exception */ private function prepare() { $params = []; foreach ($this->variables as $column => &$variable) { switch ($this->types[$column]) { case ParameterType::LARGE_OBJECT: $params[$column - 1] = [ &$variable, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), SQLSRV_SQLTYPE_VARBINARY('max'), ]; break; case ParameterType::BINARY: $params[$column - 1] = [ &$variable, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), ]; break; case ParameterType::ASCII: $params[$column - 1] = [ &$variable, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), ]; break; default: $params[$column - 1] =& $variable; break; } } $stmt = sqlsrv_prepare($this->conn, $this->sql, $params); if ($stmt === false) { throw Error::new(); } return $stmt; } } dbal/src/Driver/AbstractSQLServerDriver/Exception/PortWithoutHost.php 0000644 00000000561 15021222234 0021766 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class PortWithoutHost extends AbstractException { public static function new(): self { return new self('Connection port specified without the host'); } } dbal/src/Driver/AbstractException.php 0000644 00000002063 15021222234 0013615 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; use Exception as BaseException; use Throwable; /** * Base implementation of the {@see Exception} interface. * * @internal * * @psalm-immutable */ abstract class AbstractException extends BaseException implements Exception { /** * The SQLSTATE of the driver. */ private ?string $sqlState = null; /** * @param string $message The driver error message. * @param string|null $sqlState The SQLSTATE the driver is in at the time the error occurred, if any. * @param int $code The driver specific error code if any. * @param Throwable|null $previous The previous throwable used for the exception chaining. */ public function __construct($message, $sqlState = null, $code = 0, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->sqlState = $sqlState; } /** * {@inheritDoc} */ public function getSQLState() { return $this->sqlState; } } dbal/src/Driver/Middleware/AbstractStatementMiddleware.php 0000644 00000003747 15021222234 0017710 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function func_num_args; abstract class AbstractStatementMiddleware implements Statement { private Statement $wrappedStatement; public function __construct(Statement $wrappedStatement) { $this->wrappedStatement = $wrappedStatement; } /** * {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING) { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->wrappedStatement->bindValue($param, $value, $type); } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->wrappedStatement->bindParam($param, $variable, $type, $length); } /** * {@inheritDoc} */ public function execute($params = null): Result { return $this->wrappedStatement->execute($params); } } dbal/src/Driver/Middleware/AbstractConnectionMiddleware.php 0000644 00000005414 15021222234 0020034 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use LogicException; use function get_class; use function method_exists; use function sprintf; abstract class AbstractConnectionMiddleware implements ServerInfoAwareConnection { private Connection $wrappedConnection; public function __construct(Connection $wrappedConnection) { $this->wrappedConnection = $wrappedConnection; } public function prepare(string $sql): Statement { return $this->wrappedConnection->prepare($sql); } public function query(string $sql): Result { return $this->wrappedConnection->query($sql); } /** * {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { return $this->wrappedConnection->quote($value, $type); } public function exec(string $sql): int { return $this->wrappedConnection->exec($sql); } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name !== null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); } return $this->wrappedConnection->lastInsertId($name); } /** * {@inheritDoc} */ public function beginTransaction() { return $this->wrappedConnection->beginTransaction(); } /** * {@inheritDoc} */ public function commit() { return $this->wrappedConnection->commit(); } /** * {@inheritDoc} */ public function rollBack() { return $this->wrappedConnection->rollBack(); } /** * {@inheritDoc} */ public function getServerVersion() { if (! $this->wrappedConnection instanceof ServerInfoAwareConnection) { throw new LogicException('The underlying connection is not a ServerInfoAwareConnection'); } return $this->wrappedConnection->getServerVersion(); } /** @return resource|object */ public function getNativeConnection() { if (! method_exists($this->wrappedConnection, 'getNativeConnection')) { throw new LogicException(sprintf( 'The driver connection %s does not support accessing the native connection.', get_class($this->wrappedConnection), )); } return $this->wrappedConnection->getNativeConnection(); } } dbal/src/Driver/Middleware/AbstractResultMiddleware.php 0000644 00000002646 15021222234 0017217 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Driver\Result; abstract class AbstractResultMiddleware implements Result { private Result $wrappedResult; public function __construct(Result $result) { $this->wrappedResult = $result; } /** * {@inheritDoc} */ public function fetchNumeric() { return $this->wrappedResult->fetchNumeric(); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->wrappedResult->fetchAssociative(); } /** * {@inheritDoc} */ public function fetchOne() { return $this->wrappedResult->fetchOne(); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return $this->wrappedResult->fetchAllNumeric(); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return $this->wrappedResult->fetchAllAssociative(); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return $this->wrappedResult->fetchFirstColumn(); } public function rowCount(): int { return $this->wrappedResult->rowCount(); } public function columnCount(): int { return $this->wrappedResult->columnCount(); } public function free(): void { $this->wrappedResult->free(); } } dbal/src/Driver/Middleware/AbstractDriverMiddleware.php 0000644 00000003644 15021222234 0017173 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\VersionAwarePlatformDriver; use Doctrine\Deprecations\Deprecation; use SensitiveParameter; abstract class AbstractDriverMiddleware implements VersionAwarePlatformDriver { private Driver $wrappedDriver; public function __construct(Driver $wrappedDriver) { $this->wrappedDriver = $wrappedDriver; } /** * {@inheritDoc} */ public function connect( #[SensitiveParameter] array $params ) { return $this->wrappedDriver->connect($params); } /** * {@inheritDoc} */ public function getDatabasePlatform() { return $this->wrappedDriver->getDatabasePlatform(); } /** * {@inheritDoc} * * @deprecated Use {@link AbstractPlatform::createSchemaManager()} instead. */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5458', 'AbstractDriverMiddleware::getSchemaManager() is deprecated.' . ' Use AbstractPlatform::createSchemaManager() instead.', ); return $this->wrappedDriver->getSchemaManager($conn, $platform); } public function getExceptionConverter(): ExceptionConverter { return $this->wrappedDriver->getExceptionConverter(); } /** * {@inheritDoc} */ public function createDatabasePlatformForVersion($version) { if ($this->wrappedDriver instanceof VersionAwarePlatformDriver) { return $this->wrappedDriver->createDatabasePlatformForVersion($version); } return $this->wrappedDriver->getDatabasePlatform(); } } dbal/src/Driver/AbstractOracleDriver/EasyConnectString.php 0000644 00000005270 15021222234 0017645 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\AbstractOracleDriver; use function implode; use function is_array; use function sprintf; /** * Represents an Oracle Easy Connect string * * @link https://docs.oracle.com/database/121/NETAG/naming.htm */ final class EasyConnectString { private string $string; private function __construct(string $string) { $this->string = $string; } public function __toString(): string { return $this->string; } /** * Creates the object from an array representation * * @param mixed[] $params */ public static function fromArray(array $params): self { return new self(self::renderParams($params)); } /** * Creates the object from the given DBAL connection parameters. * * @param mixed[] $params */ public static function fromConnectionParameters(array $params): self { if (isset($params['connectstring'])) { return new self($params['connectstring']); } if (! isset($params['host'])) { return new self($params['dbname'] ?? ''); } $connectData = []; if (isset($params['servicename']) || isset($params['dbname'])) { $serviceKey = 'SID'; if (isset($params['service'])) { $serviceKey = 'SERVICE_NAME'; } $serviceName = $params['servicename'] ?? $params['dbname']; $connectData[$serviceKey] = $serviceName; } if (isset($params['instancename'])) { $connectData['INSTANCE_NAME'] = $params['instancename']; } if (! empty($params['pooled'])) { $connectData['SERVER'] = 'POOLED'; } return self::fromArray([ 'DESCRIPTION' => [ 'ADDRESS' => [ 'PROTOCOL' => 'TCP', 'HOST' => $params['host'], 'PORT' => $params['port'] ?? 1521, ], 'CONNECT_DATA' => $connectData, ], ]); } /** @param mixed[] $params */ private static function renderParams(array $params): string { $chunks = []; foreach ($params as $key => $value) { $string = self::renderValue($value); if ($string === '') { continue; } $chunks[] = sprintf('(%s=%s)', $key, $string); } return implode('', $chunks); } /** @param mixed $value */ private static function renderValue($value): string { if (is_array($value)) { return self::renderParams($value); } return (string) $value; } } dbal/src/Driver/OCI8/Middleware/InitializeSession.php 0000644 00000002241 15021222234 0016415 0 ustar 00 <?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; } }; } } dbal/src/Driver/OCI8/Connection.php 0000644 00000010123 15021222234 0012770 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\OCI8\Exception\Error; use Doctrine\DBAL\Driver\OCI8\Exception\SequenceDoesNotExist; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\SQL\Parser; use Doctrine\Deprecations\Deprecation; use function addcslashes; use function assert; use function is_float; use function is_int; use function is_resource; use function oci_commit; use function oci_parse; use function oci_rollback; use function oci_server_version; use function preg_match; use function str_replace; final class Connection implements ServerInfoAwareConnection { /** @var resource */ private $connection; private Parser $parser; private ExecutionMode $executionMode; /** * @internal The connection can be only instantiated by its driver. * * @param resource $connection */ public function __construct($connection) { $this->connection = $connection; $this->parser = new Parser(false); $this->executionMode = new ExecutionMode(); } public function getServerVersion(): string { $version = oci_server_version($this->connection); if ($version === false) { throw Error::new($this->connection); } $result = preg_match('/\s+(\d+\.\d+\.\d+\.\d+\.\d+)\s+/', $version, $matches); assert($result === 1); return $matches[1]; } /** @throws Parser\Exception */ public function prepare(string $sql): DriverStatement { $visitor = new ConvertPositionalToNamedPlaceholders(); $this->parser->parse($sql, $visitor); $statement = oci_parse($this->connection, $visitor->getSQL()); assert(is_resource($statement)); return new Statement($this->connection, $statement, $visitor->getParameterMap(), $this->executionMode); } /** * @throws Exception * @throws Parser\Exception */ public function query(string $sql): ResultInterface { return $this->prepare($sql)->execute(); } /** * {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { if (is_int($value) || is_float($value)) { return $value; } $value = str_replace("'", "''", $value); return "'" . addcslashes($value, "\000\n\r\\\032") . "'"; } /** * @throws Exception * @throws Parser\Exception */ public function exec(string $sql): int { return $this->prepare($sql)->execute()->rowCount(); } /** * {@inheritDoc} * * @param string|null $name * * @return int|false * * @throws Parser\Exception */ public function lastInsertId($name = null) { if ($name === null) { return false; } Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); $result = $this->query('SELECT ' . $name . '.CURRVAL FROM DUAL')->fetchOne(); if ($result === false) { throw SequenceDoesNotExist::new(); } return (int) $result; } public function beginTransaction(): bool { $this->executionMode->disableAutoCommit(); return true; } public function commit(): bool { if (! oci_commit($this->connection)) { throw Error::new($this->connection); } $this->executionMode->enableAutoCommit(); return true; } public function rollBack(): bool { if (! oci_rollback($this->connection)) { throw Error::new($this->connection); } $this->executionMode->enableAutoCommit(); return true; } /** @return resource */ public function getNativeConnection() { return $this->connection; } } dbal/src/Driver/OCI8/Driver.php 0000644 00000003255 15021222234 0012134 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\AbstractOracleDriver; use Doctrine\DBAL\Driver\OCI8\Exception\ConnectionFailed; use Doctrine\DBAL\Driver\OCI8\Exception\InvalidConfiguration; use SensitiveParameter; use function oci_connect; use function oci_new_connect; use function oci_pconnect; use const OCI_NO_AUTO_COMMIT; /** * A Doctrine DBAL driver for the Oracle OCI8 PHP extensions. */ final class Driver extends AbstractOracleDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $username = $params['user'] ?? ''; $password = $params['password'] ?? ''; $charset = $params['charset'] ?? ''; $sessionMode = $params['sessionMode'] ?? OCI_NO_AUTO_COMMIT; $connectionString = $this->getEasyConnectString($params); $persistent = ! empty($params['persistent']); $exclusive = ! empty($params['driverOptions']['exclusive']); if ($persistent && $exclusive) { throw InvalidConfiguration::forPersistentAndExclusive(); } if ($persistent) { $connection = @oci_pconnect($username, $password, $connectionString, $charset, $sessionMode); } elseif ($exclusive) { $connection = @oci_new_connect($username, $password, $connectionString, $charset, $sessionMode); } else { $connection = @oci_connect($username, $password, $connectionString, $charset, $sessionMode); } if ($connection === false) { throw ConnectionFailed::new(); } return new Connection($connection); } } dbal/src/Driver/OCI8/Exception/InvalidConfiguration.php 0000644 00000000642 15021222234 0016752 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class InvalidConfiguration extends AbstractException { public static function forPersistentAndExclusive(): self { return new self('The "persistent" parameter and the "exclusive" driver option are mutually exclusive'); } } dbal/src/Driver/OCI8/Exception/NonTerminatedStringLiteral.php 0000644 00000001004 15021222234 0020100 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class NonTerminatedStringLiteral extends AbstractException { public static function new(int $offset): self { return new self( sprintf( 'The statement contains non-terminated string literal starting at offset %d.', $offset, ), ); } } dbal/src/Driver/OCI8/Exception/UnknownParameterIndex.php 0000644 00000000706 15021222234 0017125 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class UnknownParameterIndex extends AbstractException { public static function new(int $index): self { return new self( sprintf('Could not find variable mapping with index %d, in the SQL statement', $index), ); } } dbal/src/Driver/OCI8/Exception/ConnectionFailed.php 0000644 00000000711 15021222234 0016035 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; use function assert; use function oci_error; /** * @internal * * @psalm-immutable */ final class ConnectionFailed extends AbstractException { public static function new(): self { $error = oci_error(); assert($error !== false); return new self($error['message'], null, $error['code']); } } dbal/src/Driver/OCI8/Exception/Error.php 0000644 00000000765 15021222234 0013733 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; use function assert; use function oci_error; /** * @internal * * @psalm-immutable */ final class Error extends AbstractException { /** @param resource $resource */ public static function new($resource): self { $error = oci_error($resource); assert($error !== false); return new self($error['message'], null, $error['code']); } } dbal/src/Driver/OCI8/Exception/SequenceDoesNotExist.php 0000644 00000000574 15021222234 0016721 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class SequenceDoesNotExist extends AbstractException { public static function new(): self { return new self('lastInsertId failed: Query was executed but no result was returned.'); } } dbal/src/Driver/OCI8/Result.php 0000644 00000005641 15021222234 0012160 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\OCI8\Exception\Error; use Doctrine\DBAL\Driver\Result as ResultInterface; use function oci_cancel; use function oci_error; use function oci_fetch_all; use function oci_fetch_array; use function oci_num_fields; use function oci_num_rows; use const OCI_ASSOC; use const OCI_FETCHSTATEMENT_BY_COLUMN; use const OCI_FETCHSTATEMENT_BY_ROW; use const OCI_NUM; use const OCI_RETURN_LOBS; use const OCI_RETURN_NULLS; final class Result implements ResultInterface { /** @var resource */ private $statement; /** * @internal The result can be only instantiated by its driver connection or statement. * * @param resource $statement */ public function __construct($statement) { $this->statement = $statement; } /** * {@inheritDoc} */ public function fetchNumeric() { return $this->fetch(OCI_NUM); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->fetch(OCI_ASSOC); } /** * {@inheritDoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return $this->fetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0]; } public function rowCount(): int { $count = oci_num_rows($this->statement); if ($count !== false) { return $count; } return 0; } public function columnCount(): int { $count = oci_num_fields($this->statement); if ($count !== false) { return $count; } return 0; } public function free(): void { oci_cancel($this->statement); } /** * @return mixed|false * * @throws Exception */ private function fetch(int $mode) { $result = oci_fetch_array($this->statement, $mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS); if ($result === false && oci_error($this->statement) !== false) { throw Error::new($this->statement); } return $result; } /** @return array<mixed> */ private function fetchAll(int $mode, int $fetchStructure): array { oci_fetch_all( $this->statement, $result, 0, -1, $mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS, ); return $result; } } dbal/src/Driver/OCI8/ConvertPositionalToNamedPlaceholders.php 0000644 00000002442 15021222234 0020156 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\SQL\Parser\Visitor; use function count; use function implode; /** * Converts positional (?) into named placeholders (:param<num>). * * Oracle does not support positional parameters, hence this method converts all * positional parameters into artificially named parameters. * * @internal This class is not covered by the backward compatibility promise */ final class ConvertPositionalToNamedPlaceholders implements Visitor { /** @var list<string> */ private array $buffer = []; /** @var array<int,string> */ private array $parameterMap = []; public function acceptOther(string $sql): void { $this->buffer[] = $sql; } public function acceptPositionalParameter(string $sql): void { $position = count($this->parameterMap) + 1; $param = ':param' . $position; $this->parameterMap[$position] = $param; $this->buffer[] = $param; } public function acceptNamedParameter(string $sql): void { $this->buffer[] = $sql; } public function getSQL(): string { return implode('', $this->buffer); } /** @return array<int,string> */ public function getParameterMap(): array { return $this->parameterMap; } } dbal/src/Driver/OCI8/Statement.php 0000644 00000011672 15021222234 0012647 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\OCI8\Exception\Error; use Doctrine\DBAL\Driver\OCI8\Exception\UnknownParameterIndex; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function func_num_args; use function is_int; use function oci_bind_by_name; use function oci_execute; use function oci_new_descriptor; use const OCI_B_BIN; use const OCI_B_BLOB; use const OCI_COMMIT_ON_SUCCESS; use const OCI_D_LOB; use const OCI_NO_AUTO_COMMIT; use const OCI_TEMP_BLOB; use const SQLT_CHR; final class Statement implements StatementInterface { /** @var resource */ private $connection; /** @var resource */ private $statement; /** @var array<int,string> */ private array $parameterMap; private ExecutionMode $executionMode; /** * @internal The statement can be only instantiated by its driver connection. * * @param resource $connection * @param resource $statement * @param array<int,string> $parameterMap */ public function __construct($connection, $statement, array $parameterMap, ExecutionMode $executionMode) { $this->connection = $connection; $this->statement = $statement; $this->parameterMap = $parameterMap; $this->executionMode = $executionMode; } /** * {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->bindParam($param, $value, $type); } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (is_int($param)) { if (! isset($this->parameterMap[$param])) { throw UnknownParameterIndex::new($param); } $param = $this->parameterMap[$param]; } if ($type === ParameterType::LARGE_OBJECT) { if ($variable !== null) { $lob = oci_new_descriptor($this->connection, OCI_D_LOB); $lob->writeTemporary($variable, OCI_TEMP_BLOB); $variable =& $lob; } else { $type = ParameterType::STRING; } } return oci_bind_by_name( $this->statement, $param, $variable, $length ?? -1, $this->convertParameterType($type), ); } /** * Converts DBAL parameter type to oci8 parameter type */ private function convertParameterType(int $type): int { switch ($type) { case ParameterType::BINARY: return OCI_B_BIN; case ParameterType::LARGE_OBJECT: return OCI_B_BLOB; default: return SQLT_CHR; } } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); foreach ($params as $key => $val) { if (is_int($key)) { $this->bindValue($key + 1, $val, ParameterType::STRING); } else { $this->bindValue($key, $val, ParameterType::STRING); } } } if ($this->executionMode->isAutoCommitEnabled()) { $mode = OCI_COMMIT_ON_SUCCESS; } else { $mode = OCI_NO_AUTO_COMMIT; } $ret = @oci_execute($this->statement, $mode); if (! $ret) { throw Error::new($this->statement); } return new Result($this->statement); } } dbal/src/Driver/OCI8/ExecutionMode.php 0000644 00000001174 15021222234 0013447 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8; /** * Encapsulates the execution mode that is shared between the connection and its statements. * * @internal This class is not covered by the backward compatibility promise */ final class ExecutionMode { private bool $isAutoCommitEnabled = true; public function enableAutoCommit(): void { $this->isAutoCommitEnabled = true; } public function disableAutoCommit(): void { $this->isAutoCommitEnabled = false; } public function isAutoCommitEnabled(): bool { return $this->isAutoCommitEnabled; } } dbal/src/Driver/Connection.php 0000644 00000003620 15021222234 0012272 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\ParameterType; /** * Connection interface. * Driver connections must implement this interface. * * @method resource|object getNativeConnection() */ interface Connection { /** * Prepares a statement for execution and returns a Statement object. * * @throws Exception */ public function prepare(string $sql): Statement; /** * Executes an SQL statement, returning a result set as a Statement object. * * @throws Exception */ public function query(string $sql): Result; /** * Quotes a string for use in a query. * * The usage of this method is discouraged. Use prepared statements * or {@see AbstractPlatform::quoteStringLiteral()} instead. * * @param mixed $value * @param int $type * * @return mixed */ public function quote($value, $type = ParameterType::STRING); /** * Executes an SQL statement and return the number of affected rows. * * @throws Exception */ public function exec(string $sql): int; /** * Returns the ID of the last inserted row or sequence value. * * @param string|null $name * * @return string|int|false * * @throws Exception */ public function lastInsertId($name = null); /** * Initiates a transaction. * * @return bool TRUE on success or FALSE on failure. * * @throws Exception */ public function beginTransaction(); /** * Commits a transaction. * * @return bool TRUE on success or FALSE on failure. * * @throws Exception */ public function commit(); /** * Rolls back the current transaction, as initiated by beginTransaction(). * * @return bool TRUE on success or FALSE on failure. * * @throws Exception */ public function rollBack(); } dbal/src/Driver/API/ExceptionConverter.php 0000644 00000001516 15021222234 0014434 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Query; interface ExceptionConverter { /** * Converts a given driver-level exception into a DBAL-level driver exception. * * Implementors should use the vendor-specific error code and SQLSTATE of the exception * and instantiate the most appropriate specialized {@see DriverException} subclass. * * @param Exception $exception The driver exception to convert. * @param Query|null $query The SQL query that triggered the exception, if any. * * @return DriverException An instance of {@see DriverException} or one of its subclasses. */ public function convert(Exception $exception, ?Query $query): DriverException; } dbal/src/Driver/API/SQLSrv/ExceptionConverter.php 0000644 00000004443 15021222234 0015570 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API\SQLSrv; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; 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 * * @link https://docs.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors */ final class ExceptionConverter implements ExceptionConverterInterface { public function convert(Exception $exception, ?Query $query): DriverException { switch ($exception->getCode()) { case 102: return new SyntaxErrorException($exception, $query); case 207: return new InvalidFieldNameException($exception, $query); case 208: return new TableNotFoundException($exception, $query); case 209: return new NonUniqueFieldNameException($exception, $query); case 515: return new NotNullConstraintViolationException($exception, $query); case 547: case 4712: return new ForeignKeyConstraintViolationException($exception, $query); case 2601: case 2627: return new UniqueConstraintViolationException($exception, $query); case 2714: return new TableExistsException($exception, $query); case 3701: case 15151: return new DatabaseObjectNotFoundException($exception, $query); case 11001: case 18456: return new ConnectionException($exception, $query); } return new DriverException($exception, $query); } } dbal/src/Driver/API/PostgreSQL/ExceptionConverter.php 0000644 00000006520 15021222234 0016437 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API\PostgreSQL; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\ConnectionException; 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\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\SchemaDoesNotExist; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Query; use function strpos; /** @internal */ final class ExceptionConverter implements ExceptionConverterInterface { /** @link http://www.postgresql.org/docs/9.4/static/errcodes-appendix.html */ public function convert(Exception $exception, ?Query $query): DriverException { switch ($exception->getSQLState()) { case '40001': case '40P01': return new DeadlockException($exception, $query); case '0A000': // Foreign key constraint violations during a TRUNCATE operation // are considered "feature not supported" in PostgreSQL. if (strpos($exception->getMessage(), 'truncate') !== false) { return new ForeignKeyConstraintViolationException($exception, $query); } break; case '23502': return new NotNullConstraintViolationException($exception, $query); case '23503': return new ForeignKeyConstraintViolationException($exception, $query); case '23505': return new UniqueConstraintViolationException($exception, $query); case '3D000': return new DatabaseDoesNotExist($exception, $query); case '3F000': return new SchemaDoesNotExist($exception, $query); case '42601': return new SyntaxErrorException($exception, $query); case '42702': return new NonUniqueFieldNameException($exception, $query); case '42703': return new InvalidFieldNameException($exception, $query); case '42P01': return new TableNotFoundException($exception, $query); case '42P07': return new TableExistsException($exception, $query); case '08006': return new ConnectionException($exception, $query); } // Prior to fixing https://bugs.php.net/bug.php?id=64705 (PHP 7.4.10), // in some cases (mainly connection errors) the PDO exception wouldn't provide a SQLSTATE via its code. // We have to match against the SQLSTATE in the error message in these cases. if ($exception->getCode() === 7 && strpos($exception->getMessage(), 'SQLSTATE[08006]') !== false) { return new ConnectionException($exception, $query); } return new DriverException($exception, $query); } } dbal/src/Driver/API/IBMDB2/ExceptionConverter.php 0000644 00000004124 15021222234 0015331 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API\IBMDB2; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; 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 * * @link https://www.ibm.com/docs/en/db2/11.5?topic=messages-sql */ final class ExceptionConverter implements ExceptionConverterInterface { public function convert(Exception $exception, ?Query $query): DriverException { switch ($exception->getCode()) { case -104: return new SyntaxErrorException($exception, $query); case -203: return new NonUniqueFieldNameException($exception, $query); case -204: return new TableNotFoundException($exception, $query); case -206: return new InvalidFieldNameException($exception, $query); case -407: return new NotNullConstraintViolationException($exception, $query); case -530: case -531: case -532: case -20356: return new ForeignKeyConstraintViolationException($exception, $query); case -601: return new TableExistsException($exception, $query); case -803: return new UniqueConstraintViolationException($exception, $query); case -1336: case -30082: return new ConnectionException($exception, $query); } return new DriverException($exception, $query); } } dbal/src/Driver/API/MySQL/ExceptionConverter.php 0000644 00000007167 15021222234 0015411 0 ustar 00 <?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); } } dbal/src/Driver/API/OCI/ExceptionConverter.php 0000644 00000004716 15021222234 0015053 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API\OCI; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DatabaseDoesNotExist; use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; 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 http://www.dba-oracle.com/t_error_code_list.htm */ public function convert(Exception $exception, ?Query $query): DriverException { switch ($exception->getCode()) { case 1: case 2299: case 38911: return new UniqueConstraintViolationException($exception, $query); case 904: return new InvalidFieldNameException($exception, $query); case 918: case 960: return new NonUniqueFieldNameException($exception, $query); case 923: return new SyntaxErrorException($exception, $query); case 942: return new TableNotFoundException($exception, $query); case 955: return new TableExistsException($exception, $query); case 1017: case 12545: return new ConnectionException($exception, $query); case 1400: return new NotNullConstraintViolationException($exception, $query); case 1918: return new DatabaseDoesNotExist($exception, $query); case 2289: case 2443: case 4080: return new DatabaseObjectNotFoundException($exception, $query); case 2266: case 2291: case 2292: return new ForeignKeyConstraintViolationException($exception, $query); } return new DriverException($exception, $query); } } dbal/src/Driver/API/SQLite/ExceptionConverter.php 0000644 00000006444 15021222234 0015602 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\API\SQLite; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Exception\ConnectionException; 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\ReadOnlyException; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Query; use function strpos; /** @internal */ final class ExceptionConverter implements ExceptionConverterInterface { /** @link http://www.sqlite.org/c3ref/c_abort.html */ public function convert(Exception $exception, ?Query $query): DriverException { if (strpos($exception->getMessage(), 'database is locked') !== false) { return new LockWaitTimeoutException($exception, $query); } if ( strpos($exception->getMessage(), 'must be unique') !== false || strpos($exception->getMessage(), 'is not unique') !== false || strpos($exception->getMessage(), 'are not unique') !== false || strpos($exception->getMessage(), 'UNIQUE constraint failed') !== false ) { return new UniqueConstraintViolationException($exception, $query); } if ( strpos($exception->getMessage(), 'may not be NULL') !== false || strpos($exception->getMessage(), 'NOT NULL constraint failed') !== false ) { return new NotNullConstraintViolationException($exception, $query); } if (strpos($exception->getMessage(), 'no such table:') !== false) { return new TableNotFoundException($exception, $query); } if (strpos($exception->getMessage(), 'already exists') !== false) { return new TableExistsException($exception, $query); } if (strpos($exception->getMessage(), 'has no column named') !== false) { return new InvalidFieldNameException($exception, $query); } if (strpos($exception->getMessage(), 'ambiguous column name') !== false) { return new NonUniqueFieldNameException($exception, $query); } if (strpos($exception->getMessage(), 'syntax error') !== false) { return new SyntaxErrorException($exception, $query); } if (strpos($exception->getMessage(), 'attempt to write a readonly database') !== false) { return new ReadOnlyException($exception, $query); } if (strpos($exception->getMessage(), 'unable to open database file') !== false) { return new ConnectionException($exception, $query); } if (strpos($exception->getMessage(), 'FOREIGN KEY constraint failed') !== false) { return new ForeignKeyConstraintViolationException($exception, $query); } return new DriverException($exception, $query); } } dbal/src/Driver/API/SQLite/UserDefinedFunctions.php 0000644 00000004367 15021222234 0016044 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\API\SQLite; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\Deprecations\Deprecation; use function array_merge; use function strpos; /** * User-defined SQLite functions. * * @internal */ final class UserDefinedFunctions { private const DEFAULT_FUNCTIONS = [ 'sqrt' => ['callback' => [SqlitePlatform::class, 'udfSqrt'], 'numArgs' => 1], 'mod' => ['callback' => [SqlitePlatform::class, 'udfMod'], 'numArgs' => 2], 'locate' => ['callback' => [SqlitePlatform::class, 'udfLocate'], 'numArgs' => -1], ]; /** * @param callable(string, callable, int): bool $callback * @param array<string, array{callback: callable, numArgs: int}> $additionalFunctions */ public static function register(callable $callback, array $additionalFunctions = []): void { $userDefinedFunctions = array_merge(self::DEFAULT_FUNCTIONS, $additionalFunctions); foreach ($userDefinedFunctions as $function => $data) { $callback($function, $data['callback'], $data['numArgs']); } } /** * User-defined function that implements MOD(). * * @param int $a * @param int $b */ public static function mod($a, $b): int { return $a % $b; } /** * User-defined function that implements LOCATE(). * * @param string $str * @param string $substr * @param int $offset */ public static function locate($str, $substr, $offset = 0): int { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5749', 'Relying on DBAL\'s emulated LOCATE() function is deprecated. ' . 'Use INSTR() or %s::getLocateExpression() instead.', AbstractPlatform::class, ); // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions. // So we have to make them compatible if an offset is given. if ($offset > 0) { $offset -= 1; } $pos = strpos($str, $substr, $offset); if ($pos !== false) { return $pos + 1; } return 0; } } dbal/src/Driver/AbstractPostgreSQLDriver.php 0000644 00000005565 15021222234 0015050 0 ustar 00 <?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(); } } dbal/src/Driver/Exception.php 0000644 00000000624 15021222234 0012132 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; use Throwable; /** @psalm-immutable */ interface Exception extends Throwable { /** * Returns the SQLSTATE the driver was in at the time the error occurred. * * Returns null if the driver does not provide a SQLSTATE for the error occurred. * * @return string|null */ public function getSQLState(); } dbal/src/Driver/Exception/UnknownParameterType.php 0000644 00000000637 15021222234 0016300 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class UnknownParameterType extends AbstractException { /** @param mixed $type */ public static function new($type): self { return new self(sprintf('Unknown parameter type, %d given.', $type)); } } dbal/src/Driver/AbstractSQLiteDriver/Middleware/EnableForeignKeys.php 0000644 00000001456 15021222234 0021612 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\AbstractSQLiteDriver\Middleware; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use SensitiveParameter; class EnableForeignKeys 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('PRAGMA foreign_keys=ON'); return $connection; } }; } } dbal/src/Driver/Result.php 0000644 00000004701 15021222234 0011452 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; /** * Driver-level statement execution result. */ interface Result { /** * 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(); /** * 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(); /** * 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(); /** * Returns an array containing all of the result rows represented as numeric arrays. * * @return list<list<mixed>> * * @throws Exception */ public function fetchAllNumeric(): array; /** * Returns an array containing all of the result rows represented as associative arrays. * * @return list<array<string,mixed>> * * @throws Exception */ public function fetchAllAssociative(): array; /** * Returns an array containing the values of the first column of the result. * * @return list<mixed> * * @throws Exception */ public function fetchFirstColumn(): array; /** * Returns the number of rows affected by the DELETE, INSERT, or UPDATE statement that produced the result. * * If the statement executed a SELECT query or a similar platform-specific SQL (e.g. DESCRIBE, SHOW, etc.), * some database drivers may return the number of rows returned by that query. However, this behaviour * is not guaranteed for all drivers and should not be relied on in portable applications. * * @return int The number of rows. * * @throws Exception */ public function rowCount(): int; /** * Returns the number of columns in the result * * @return int The number of columns in the result. If the columns cannot be counted, * this method must return 0. * * @throws Exception */ public function columnCount(): int; /** * Discards the non-fetched portion of the result, enabling the originating statement to be executed again. */ public function free(): void; } dbal/src/Driver/Middleware.php 0000644 00000000250 15021222234 0012244 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver; interface Middleware { public function wrap(Driver $driver): Driver; } dbal/src/Driver/Mysqli/Connection.php 0000644 00000006710 15021222234 0013553 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use mysqli; use mysqli_sql_exception; final class Connection implements ServerInfoAwareConnection { /** * Name of the option to set connection flags */ public const OPTION_FLAGS = 'flags'; private mysqli $connection; /** @internal The connection can be only instantiated by its driver. */ public function __construct(mysqli $connection) { $this->connection = $connection; } /** * Retrieves mysqli native resource handle. * * Could be used if part of your application is not using DBAL. * * @deprecated Call {@see getNativeConnection()} instead. */ public function getWrappedResourceHandle(): mysqli { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5037', '%s is deprecated, call getNativeConnection() instead.', __METHOD__, ); return $this->getNativeConnection(); } public function getServerVersion(): string { return $this->connection->get_server_info(); } public function prepare(string $sql): DriverStatement { try { $stmt = $this->connection->prepare($sql); } catch (mysqli_sql_exception $e) { throw ConnectionError::upcast($e); } if ($stmt === false) { throw ConnectionError::new($this->connection); } return new Statement($stmt); } public function query(string $sql): ResultInterface { return $this->prepare($sql)->execute(); } /** * {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { return "'" . $this->connection->escape_string($value) . "'"; } public function exec(string $sql): int { try { $result = $this->connection->query($sql); } catch (mysqli_sql_exception $e) { throw ConnectionError::upcast($e); } if ($result === false) { throw ConnectionError::new($this->connection); } return $this->connection->affected_rows; } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name !== null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); } return $this->connection->insert_id; } public function beginTransaction(): bool { $this->connection->begin_transaction(); return true; } public function commit(): bool { try { return $this->connection->commit(); } catch (mysqli_sql_exception $e) { return false; } } public function rollBack(): bool { try { return $this->connection->rollback(); } catch (mysqli_sql_exception $e) { return false; } } public function getNativeConnection(): mysqli { return $this->connection; } } dbal/src/Driver/Mysqli/Driver.php 0000644 00000006212 15021222234 0012704 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\AbstractMySQLDriver; use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionFailed; use Doctrine\DBAL\Driver\Mysqli\Exception\HostRequired; use Doctrine\DBAL\Driver\Mysqli\Initializer\Charset; use Doctrine\DBAL\Driver\Mysqli\Initializer\Options; use Doctrine\DBAL\Driver\Mysqli\Initializer\Secure; use Generator; use mysqli; use mysqli_sql_exception; use SensitiveParameter; final class Driver extends AbstractMySQLDriver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { if (! empty($params['persistent'])) { if (! isset($params['host'])) { throw HostRequired::forPersistentConnection(); } $host = 'p:' . $params['host']; } else { $host = $params['host'] ?? null; } $connection = new mysqli(); foreach ($this->compilePreInitializers($params) as $initializer) { $initializer->initialize($connection); } try { $success = @$connection->real_connect( $host, $params['user'] ?? null, $params['password'] ?? null, $params['dbname'] ?? null, $params['port'] ?? null, $params['unix_socket'] ?? null, $params['driverOptions'][Connection::OPTION_FLAGS] ?? 0, ); } catch (mysqli_sql_exception $e) { throw ConnectionFailed::upcast($e); } if (! $success) { throw ConnectionFailed::new($connection); } foreach ($this->compilePostInitializers($params) as $initializer) { $initializer->initialize($connection); } return new Connection($connection); } /** * @param array<string, mixed> $params * * @return Generator<int, Initializer> */ private function compilePreInitializers( #[SensitiveParameter] array $params ): Generator { unset($params['driverOptions'][Connection::OPTION_FLAGS]); if (isset($params['driverOptions']) && $params['driverOptions'] !== []) { yield new Options($params['driverOptions']); } if ( ! isset($params['ssl_key']) && ! isset($params['ssl_cert']) && ! isset($params['ssl_ca']) && ! isset($params['ssl_capath']) && ! isset($params['ssl_cipher']) ) { return; } yield new Secure( $params['ssl_key'] ?? '', $params['ssl_cert'] ?? '', $params['ssl_ca'] ?? '', $params['ssl_capath'] ?? '', $params['ssl_cipher'] ?? '', ); } /** * @param array<string, mixed> $params * * @return Generator<int, Initializer> */ private function compilePostInitializers( #[SensitiveParameter] array $params ): Generator { if (! isset($params['charset'])) { return; } yield new Charset($params['charset']); } } dbal/src/Driver/Mysqli/Exception/ConnectionError.php 0000644 00000001375 15021222234 0016525 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use mysqli; use mysqli_sql_exception; use ReflectionProperty; /** * @internal * * @psalm-immutable */ final class ConnectionError extends AbstractException { public static function new(mysqli $connection): self { return new self($connection->error, $connection->sqlstate, $connection->errno); } public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); $p->setAccessible(true); return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); } } dbal/src/Driver/Mysqli/Exception/NonStreamResourceUsedAsLargeObject.php 0000644 00000000746 15021222234 0022242 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class NonStreamResourceUsedAsLargeObject extends AbstractException { public static function new(int $parameter): self { return new self( sprintf('The resource passed as a LARGE_OBJECT parameter #%d must be of type "stream"', $parameter), ); } } dbal/src/Driver/Mysqli/Exception/InvalidCharset.php 0000644 00000002021 15021222234 0016301 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use mysqli; use mysqli_sql_exception; use ReflectionProperty; use function sprintf; /** * @internal * * @psalm-immutable */ final class InvalidCharset extends AbstractException { public static function fromCharset(mysqli $connection, string $charset): self { return new self( sprintf('Failed to set charset "%s": %s', $charset, $connection->error), $connection->sqlstate, $connection->errno, ); } public static function upcast(mysqli_sql_exception $exception, string $charset): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); $p->setAccessible(true); return new self( sprintf('Failed to set charset "%s": %s', $charset, $exception->getMessage()), $p->getValue($exception), (int) $exception->getCode(), $exception, ); } } dbal/src/Driver/Mysqli/Exception/ConnectionFailed.php 0000644 00000001521 15021222234 0016611 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use mysqli; use mysqli_sql_exception; use ReflectionProperty; use function assert; /** * @internal * * @psalm-immutable */ final class ConnectionFailed extends AbstractException { public static function new(mysqli $connection): self { $error = $connection->connect_error; assert($error !== null); return new self($error, 'HY000', $connection->connect_errno); } public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); $p->setAccessible(true); return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); } } dbal/src/Driver/Mysqli/Exception/FailedReadingStreamOffset.php 0000644 00000000657 15021222234 0020417 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class FailedReadingStreamOffset extends AbstractException { public static function new(int $parameter): self { return new self(sprintf('Failed reading the stream resource for parameter #%d.', $parameter)); } } dbal/src/Driver/Mysqli/Exception/HostRequired.php 0000644 00000000603 15021222234 0016023 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class HostRequired extends AbstractException { public static function forPersistentConnection(): self { return new self('The "host" parameter is required for a persistent connection'); } } dbal/src/Driver/Mysqli/Exception/InvalidOption.php 0000644 00000000734 15021222234 0016171 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** * @internal * * @psalm-immutable */ final class InvalidOption extends AbstractException { /** @param mixed $value */ public static function fromOption(int $option, $value): self { return new self( sprintf('Failed to set option %d with value "%s"', $option, $value), ); } } dbal/src/Driver/Mysqli/Exception/StatementError.php 0000644 00000001402 15021222234 0016361 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\AbstractException; use mysqli_sql_exception; use mysqli_stmt; use ReflectionProperty; /** * @internal * * @psalm-immutable */ final class StatementError extends AbstractException { public static function new(mysqli_stmt $statement): self { return new self($statement->error, $statement->sqlstate, $statement->errno); } public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); $p->setAccessible(true); return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception); } } dbal/src/Driver/Mysqli/Result.php 0000644 00000011313 15021222234 0012725 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError; use Doctrine\DBAL\Driver\Result as ResultInterface; use mysqli_sql_exception; use mysqli_stmt; use function array_column; use function array_combine; use function array_fill; use function count; final class Result implements ResultInterface { private mysqli_stmt $statement; /** * Whether the statement result has columns. The property should be used only after the result metadata * has been fetched ({@see $metadataFetched}). Otherwise, the property value is undetermined. */ private bool $hasColumns = false; /** * Mapping of statement result column indexes to their names. The property should be used only * if the statement result has columns ({@see $hasColumns}). Otherwise, the property value is undetermined. * * @var array<int,string> */ private array $columnNames = []; /** @var mixed[] */ private array $boundValues = []; /** * @internal The result can be only instantiated by its driver connection or statement. * * @throws Exception */ public function __construct(mysqli_stmt $statement) { $this->statement = $statement; $meta = $statement->result_metadata(); if ($meta === false) { return; } $this->hasColumns = true; $this->columnNames = array_column($meta->fetch_fields(), 'name'); $meta->free(); // Store result of every execution which has it. Otherwise it will be impossible // to execute a new statement in case if the previous one has non-fetched rows // @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html $this->statement->store_result(); // Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql, // it will have to allocate as much memory as it may be needed for the given column type // (e.g. for a LONGBLOB column it's 4 gigabytes) // @link https://bugs.php.net/bug.php?id=51386#1270673122 // // Make sure that the values are bound after each execution. Otherwise, if free() has been // previously called on the result, the values are unbound making the statement unusable. // // It's also important that row values are bound after _each_ call to store_result(). Otherwise, // if mysqli is compiled with libmysql, subsequently fetched string values will get truncated // to the length of the ones fetched during the previous execution. $this->boundValues = array_fill(0, count($this->columnNames), null); // The following is necessary as PHP cannot handle references to properties properly $refs = &$this->boundValues; if (! $this->statement->bind_result(...$refs)) { throw StatementError::new($this->statement); } } /** * {@inheritDoc} */ public function fetchNumeric() { try { $ret = $this->statement->fetch(); } catch (mysqli_sql_exception $e) { throw StatementError::upcast($e); } if ($ret === false) { throw StatementError::new($this->statement); } if ($ret === null) { return false; } $values = []; foreach ($this->boundValues as $v) { $values[] = $v; } return $values; } /** * {@inheritDoc} */ public function fetchAssociative() { $values = $this->fetchNumeric(); if ($values === false) { return false; } return array_combine($this->columnNames, $values); } /** * {@inheritDoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } public function rowCount(): int { if ($this->hasColumns) { return $this->statement->num_rows; } return $this->statement->affected_rows; } public function columnCount(): int { return $this->statement->field_count; } public function free(): void { $this->statement->free_result(); } } dbal/src/Driver/Mysqli/Initializer.php 0000644 00000000353 15021222234 0013734 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\Exception; use mysqli; interface Initializer { /** @throws Exception */ public function initialize(mysqli $connection): void; } dbal/src/Driver/Mysqli/Initializer/Options.php 0000644 00000001370 15021222234 0015367 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Initializer; use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidOption; use Doctrine\DBAL\Driver\Mysqli\Initializer; use mysqli; use function mysqli_options; final class Options implements Initializer { /** @var array<int,mixed> */ private array $options; /** @param array<int,mixed> $options */ public function __construct(array $options) { $this->options = $options; } public function initialize(mysqli $connection): void { foreach ($this->options as $option => $value) { if (! mysqli_options($connection, $option, $value)) { throw InvalidOption::fromOption($option, $value); } } } } dbal/src/Driver/Mysqli/Initializer/Secure.php 0000644 00000001526 15021222234 0015165 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Initializer; use Doctrine\DBAL\Driver\Mysqli\Initializer; use mysqli; use SensitiveParameter; final class Secure implements Initializer { private string $key; private string $cert; private string $ca; private string $capath; private string $cipher; public function __construct( #[SensitiveParameter] string $key, string $cert, string $ca, string $capath, string $cipher ) { $this->key = $key; $this->cert = $cert; $this->ca = $ca; $this->capath = $capath; $this->cipher = $cipher; } public function initialize(mysqli $connection): void { $connection->ssl_set($this->key, $this->cert, $this->ca, $this->capath, $this->cipher); } } dbal/src/Driver/Mysqli/Initializer/Charset.php 0000644 00000001431 15021222234 0015323 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Initializer; use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidCharset; use Doctrine\DBAL\Driver\Mysqli\Initializer; use mysqli; use mysqli_sql_exception; final class Charset implements Initializer { private string $charset; public function __construct(string $charset) { $this->charset = $charset; } public function initialize(mysqli $connection): void { try { $success = $connection->set_charset($this->charset); } catch (mysqli_sql_exception $e) { throw InvalidCharset::upcast($e, $this->charset); } if ($success) { return; } throw InvalidCharset::fromCharset($connection, $this->charset); } } dbal/src/Driver/Mysqli/Statement.php 0000644 00000015605 15021222234 0013423 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\Driver\Mysqli\Exception\FailedReadingStreamOffset; use Doctrine\DBAL\Driver\Mysqli\Exception\NonStreamResourceUsedAsLargeObject; use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use mysqli_sql_exception; use mysqli_stmt; use function array_fill; use function assert; use function count; use function feof; use function fread; use function func_num_args; use function get_resource_type; use function is_int; use function is_resource; use function str_repeat; final class Statement implements StatementInterface { private const PARAM_TYPE_MAP = [ ParameterType::ASCII => 's', ParameterType::STRING => 's', ParameterType::BINARY => 's', ParameterType::BOOLEAN => 'i', ParameterType::NULL => 's', ParameterType::INTEGER => 'i', ParameterType::LARGE_OBJECT => 'b', ]; private mysqli_stmt $stmt; /** @var mixed[] */ private array $boundValues; private string $types; /** * Contains ref values for bindValue(). * * @var mixed[] */ private array $values = []; /** @internal The statement can be only instantiated by its driver connection. */ public function __construct(mysqli_stmt $stmt) { $this->stmt = $stmt; $paramCount = $this->stmt->param_count; $this->types = str_repeat('s', $paramCount); $this->boundValues = array_fill(1, $paramCount, null); } /** * @deprecated Use {@see bindValue()} instead. * * {@inheritDoc} * * @psalm-assert ParameterType::* $type */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (! isset(self::PARAM_TYPE_MAP[$type])) { throw UnknownParameterType::new($type); } $this->boundValues[$param] =& $variable; $this->types[$param - 1] = self::PARAM_TYPE_MAP[$type]; return true; } /** * {@inheritDoc} * * @psalm-assert ParameterType::* $type */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (! isset(self::PARAM_TYPE_MAP[$type])) { throw UnknownParameterType::new($type); } $this->values[$param] = $value; $this->boundValues[$param] =& $this->values[$param]; $this->types[$param - 1] = self::PARAM_TYPE_MAP[$type]; return true; } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); } if ($params !== null && count($params) > 0) { if (! $this->bindUntypedValues($params)) { throw StatementError::new($this->stmt); } } elseif (count($this->boundValues) > 0) { $this->bindTypedParameters(); } try { $result = $this->stmt->execute(); } catch (mysqli_sql_exception $e) { throw StatementError::upcast($e); } if (! $result) { throw StatementError::new($this->stmt); } return new Result($this->stmt); } /** * Binds parameters with known types previously bound to the statement * * @throws Exception */ private function bindTypedParameters(): void { $streams = $values = []; $types = $this->types; foreach ($this->boundValues as $parameter => $value) { assert(is_int($parameter)); if (! isset($types[$parameter - 1])) { $types[$parameter - 1] = self::PARAM_TYPE_MAP[ParameterType::STRING]; } if ($types[$parameter - 1] === self::PARAM_TYPE_MAP[ParameterType::LARGE_OBJECT]) { if (is_resource($value)) { if (get_resource_type($value) !== 'stream') { throw NonStreamResourceUsedAsLargeObject::new($parameter); } $streams[$parameter] = $value; $values[$parameter] = null; continue; } $types[$parameter - 1] = self::PARAM_TYPE_MAP[ParameterType::STRING]; } $values[$parameter] = $value; } if (! $this->stmt->bind_param($types, ...$values)) { throw StatementError::new($this->stmt); } $this->sendLongData($streams); } /** * Handle $this->_longData after regular query parameters have been bound * * @param array<int, resource> $streams * * @throws Exception */ private function sendLongData(array $streams): void { foreach ($streams as $paramNr => $stream) { while (! feof($stream)) { $chunk = fread($stream, 8192); if ($chunk === false) { throw FailedReadingStreamOffset::new($paramNr); } if (! $this->stmt->send_long_data($paramNr - 1, $chunk)) { throw StatementError::new($this->stmt); } } } } /** * Binds a array of values to bound parameters. * * @param mixed[] $values */ private function bindUntypedValues(array $values): bool { return $this->stmt->bind_param(str_repeat('s', count($values)), ...$values); } } dbal/src/Driver/IBMDB2/Connection.php 0000644 00000006622 15021222234 0013176 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionError; use Doctrine\DBAL\Driver\IBMDB2\Exception\PrepareFailed; use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use stdClass; use function assert; use function db2_autocommit; use function db2_commit; use function db2_escape_string; use function db2_exec; use function db2_last_insert_id; use function db2_num_rows; use function db2_prepare; use function db2_rollback; use function db2_server_info; use function error_get_last; use const DB2_AUTOCOMMIT_OFF; use const DB2_AUTOCOMMIT_ON; final class Connection implements ServerInfoAwareConnection { /** @var resource */ private $connection; /** * @internal The connection can be only instantiated by its driver. * * @param resource $connection */ public function __construct($connection) { $this->connection = $connection; } /** * {@inheritDoc} */ public function getServerVersion() { $serverInfo = db2_server_info($this->connection); assert($serverInfo instanceof stdClass); return $serverInfo->DBMS_VER; } public function prepare(string $sql): DriverStatement { $stmt = @db2_prepare($this->connection, $sql); if ($stmt === false) { throw PrepareFailed::new(error_get_last()); } return new Statement($stmt); } public function query(string $sql): ResultInterface { return $this->prepare($sql)->execute(); } /** * {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { $value = db2_escape_string($value); if ($type === ParameterType::INTEGER) { return $value; } return "'" . $value . "'"; } public function exec(string $sql): int { $stmt = @db2_exec($this->connection, $sql); if ($stmt === false) { throw StatementError::new(); } return db2_num_rows($stmt); } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name !== null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); } return db2_last_insert_id($this->connection) ?? false; } public function beginTransaction(): bool { return db2_autocommit($this->connection, DB2_AUTOCOMMIT_OFF); } public function commit(): bool { if (! db2_commit($this->connection)) { throw ConnectionError::new($this->connection); } return db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON); } public function rollBack(): bool { if (! db2_rollback($this->connection)) { throw ConnectionError::new($this->connection); } return db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON); } /** @return resource */ public function getNativeConnection() { return $this->connection; } } dbal/src/Driver/IBMDB2/Driver.php 0000644 00000002102 15021222234 0012317 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\AbstractDB2Driver; use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionFailed; use SensitiveParameter; use function db2_connect; use function db2_pconnect; final class Driver extends AbstractDB2Driver { /** * {@inheritDoc} * * @return Connection */ public function connect( #[SensitiveParameter] array $params ) { $dataSourceName = DataSourceName::fromConnectionParameters($params)->toString(); $username = $params['user'] ?? ''; $password = $params['password'] ?? ''; $driverOptions = $params['driverOptions'] ?? []; if (! empty($params['persistent'])) { $connection = db2_pconnect($dataSourceName, $username, $password, $driverOptions); } else { $connection = db2_connect($dataSourceName, $username, $password, $driverOptions); } if ($connection === false) { throw ConnectionFailed::new(); } return new Connection($connection); } } dbal/src/Driver/IBMDB2/Exception/ConnectionError.php 0000644 00000001233 15021222234 0016137 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; use function db2_conn_error; use function db2_conn_errormsg; /** * @internal * * @psalm-immutable */ final class ConnectionError extends AbstractException { /** @param resource $connection */ public static function new($connection): self { $message = db2_conn_errormsg($connection); $sqlState = db2_conn_error($connection); return Factory::create($message, static function (int $code) use ($message, $sqlState): self { return new self($message, $sqlState, $code); }); } } dbal/src/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php 0000644 00000001057 15021222234 0017742 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class CannotCopyStreamToStream extends AbstractException { /** @psalm-param array{message: string}|null $error */ public static function new(?array $error): self { $message = 'Could not copy source stream to temporary file'; if ($error !== null) { $message .= ': ' . $error['message']; } return new self($message); } } dbal/src/Driver/IBMDB2/Exception/PrepareFailed.php 0000644 00000000743 15021222234 0015536 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class PrepareFailed extends AbstractException { /** @psalm-param array{message: string}|null $error */ public static function new(?array $error): self { if ($error === null) { return new self('Unknown error'); } return new self($error['message']); } } dbal/src/Driver/IBMDB2/Exception/ConnectionFailed.php 0000644 00000001124 15021222234 0016231 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; use function db2_conn_error; use function db2_conn_errormsg; /** * @internal * * @psalm-immutable */ final class ConnectionFailed extends AbstractException { public static function new(): self { $message = db2_conn_errormsg(); $sqlState = db2_conn_error(); return Factory::create($message, static function (int $code) use ($message, $sqlState): self { return new self($message, $sqlState, $code); }); } } dbal/src/Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php 0000644 00000001041 15021222234 0020074 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class CannotCreateTemporaryFile extends AbstractException { /** @psalm-param array{message: string}|null $error */ public static function new(?array $error): self { $message = 'Could not create temporary file'; if ($error !== null) { $message .= ': ' . $error['message']; } return new self($message); } } dbal/src/Driver/IBMDB2/Exception/Factory.php 0000644 00000001172 15021222234 0014437 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; use function preg_match; /** * @internal * * @psalm-immutable */ final class Factory { /** * @param callable(int): T $constructor * * @return T * * @template T of AbstractException */ public static function create(string $message, callable $constructor): AbstractException { $code = 0; if (preg_match('/ SQL(\d+)N /', $message, $matches) === 1) { $code = -(int) $matches[1]; } return $constructor($code); } } dbal/src/Driver/IBMDB2/Exception/StatementError.php 0000644 00000001477 15021222234 0016016 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; use function db2_stmt_error; use function db2_stmt_errormsg; /** * @internal * * @psalm-immutable */ final class StatementError extends AbstractException { /** @param resource|null $statement */ public static function new($statement = null): self { if ($statement !== null) { $message = db2_stmt_errormsg($statement); $sqlState = db2_stmt_error($statement); } else { $message = db2_stmt_errormsg(); $sqlState = db2_stmt_error(); } return Factory::create($message, static function (int $code) use ($message, $sqlState): self { return new self($message, $sqlState, $code); }); } } dbal/src/Driver/IBMDB2/Result.php 0000644 00000004400 15021222234 0012345 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; use Doctrine\DBAL\Driver\Result as ResultInterface; use function db2_fetch_array; use function db2_fetch_assoc; use function db2_free_result; use function db2_num_fields; use function db2_num_rows; use function db2_stmt_error; final class Result implements ResultInterface { /** @var resource */ private $statement; /** * @internal The result can be only instantiated by its driver connection or statement. * * @param resource $statement */ public function __construct($statement) { $this->statement = $statement; } /** * {@inheritDoc} */ public function fetchNumeric() { $row = @db2_fetch_array($this->statement); if ($row === false && db2_stmt_error($this->statement) !== '02000') { throw StatementError::new($this->statement); } return $row; } /** * {@inheritDoc} */ public function fetchAssociative() { $row = @db2_fetch_assoc($this->statement); if ($row === false && db2_stmt_error($this->statement) !== '02000') { throw StatementError::new($this->statement); } return $row; } /** * {@inheritDoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } public function rowCount(): int { return @db2_num_rows($this->statement); } public function columnCount(): int { $count = db2_num_fields($this->statement); if ($count !== false) { return $count; } return 0; } public function free(): void { db2_free_result($this->statement); } } dbal/src/Driver/IBMDB2/DataSourceName.php 0000644 00000003447 15021222234 0013734 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2; use SensitiveParameter; use function implode; use function sprintf; use function strpos; /** * IBM DB2 DSN */ final class DataSourceName { private string $string; private function __construct( #[SensitiveParameter] string $string ) { $this->string = $string; } public function toString(): string { return $this->string; } /** * Creates the object from an array representation * * @param array<string,mixed> $params */ public static function fromArray( #[SensitiveParameter] array $params ): self { $chunks = []; foreach ($params as $key => $value) { $chunks[] = sprintf('%s=%s', $key, $value); } return new self(implode(';', $chunks)); } /** * Creates the object from the given DBAL connection parameters. * * @param array<string,mixed> $params */ public static function fromConnectionParameters( #[SensitiveParameter] array $params ): self { if (isset($params['dbname']) && strpos($params['dbname'], '=') !== false) { return new self($params['dbname']); } $dsnParams = []; foreach ( [ 'host' => 'HOSTNAME', 'port' => 'PORT', 'protocol' => 'PROTOCOL', 'dbname' => 'DATABASE', 'user' => 'UID', 'password' => 'PWD', ] as $dbalParam => $dsnParam ) { if (! isset($params[$dbalParam])) { continue; } $dsnParams[$dsnParam] = $params[$dbalParam]; } return self::fromArray($dsnParams); } } dbal/src/Driver/IBMDB2/Statement.php 0000644 00000013431 15021222234 0013037 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCopyStreamToStream; use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCreateTemporaryFile; use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function assert; use function db2_bind_param; use function db2_execute; use function error_get_last; use function fclose; use function func_num_args; use function is_int; use function is_resource; use function stream_copy_to_stream; use function stream_get_meta_data; use function tmpfile; use const DB2_BINARY; use const DB2_CHAR; use const DB2_LONG; use const DB2_PARAM_FILE; use const DB2_PARAM_IN; final class Statement implements StatementInterface { /** @var resource */ private $stmt; /** @var mixed[] */ private array $parameters = []; /** * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement * and the temporary file handle bound to the underlying statement * * @var array<int,string|resource|null> */ private array $lobs = []; /** * @internal The statement can be only instantiated by its driver connection. * * @param resource $stmt */ public function __construct($stmt) { $this->stmt = $stmt; } /** * {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->bindParam($param, $value, $type); } /** * {@inheritDoc} * * @deprecated Use {@see bindValue()} instead. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); assert(is_int($param)); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } switch ($type) { case ParameterType::INTEGER: $this->bind($param, $variable, DB2_PARAM_IN, DB2_LONG); break; case ParameterType::LARGE_OBJECT: $this->lobs[$param] = &$variable; break; default: $this->bind($param, $variable, DB2_PARAM_IN, DB2_CHAR); break; } return true; } /** * @param int $position Parameter position * @param mixed $variable * * @throws Exception */ private function bind($position, &$variable, int $parameterType, int $dataType): void { $this->parameters[$position] =& $variable; if (! db2_bind_param($this->stmt, $position, '', $parameterType, $dataType)) { throw StatementError::new($this->stmt); } } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); } $handles = $this->bindLobs(); $result = @db2_execute($this->stmt, $params ?? $this->parameters); foreach ($handles as $handle) { fclose($handle); } $this->lobs = []; if ($result === false) { throw StatementError::new($this->stmt); } return new Result($this->stmt); } /** * @return list<resource> * * @throws Exception */ private function bindLobs(): array { $handles = []; foreach ($this->lobs as $param => $value) { if (is_resource($value)) { $handle = $handles[] = $this->createTemporaryFile(); $path = stream_get_meta_data($handle)['uri']; $this->copyStreamToStream($value, $handle); $this->bind($param, $path, DB2_PARAM_FILE, DB2_BINARY); } else { $this->bind($param, $value, DB2_PARAM_IN, DB2_CHAR); } unset($value); } return $handles; } /** * @return resource * * @throws Exception */ private function createTemporaryFile() { $handle = @tmpfile(); if ($handle === false) { throw CannotCreateTemporaryFile::new(error_get_last()); } return $handle; } /** * @param resource $source * @param resource $target * * @throws Exception */ private function copyStreamToStream($source, $target): void { if (@stream_copy_to_stream($source, $target) === false) { throw CannotCopyStreamToStream::new(error_get_last()); } } } dbal/src/Driver/AbstractSQLServerDriver.php 0000644 00000003025 15021222234 0014660 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; use Doctrine\DBAL\Driver\API\SQLSrv\ExceptionConverter; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Schema\SQLServerSchemaManager; use Doctrine\Deprecations\Deprecation; use function assert; /** * Abstract base implementation of the {@see Driver} interface for Microsoft SQL Server based drivers. */ abstract class AbstractSQLServerDriver implements Driver { /** * {@inheritDoc} */ public function getDatabasePlatform() { return new SQLServer2012Platform(); } /** * {@inheritDoc} * * @deprecated Use {@link SQLServerPlatform::createSchemaManager()} instead. */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5458', 'AbstractSQLServerDriver::getSchemaManager() is deprecated.' . ' Use SQLServerPlatform::createSchemaManager() instead.', ); assert($platform instanceof SQLServerPlatform); return new SQLServerSchemaManager($conn, $platform); } public function getExceptionConverter(): ExceptionConverterInterface { return new ExceptionConverter(); } } dbal/src/Driver/AbstractSQLiteDriver.php 0000644 00000002617 15021222234 0014201 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Driver\API\SQLite; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\SqliteSchemaManager; use Doctrine\Deprecations\Deprecation; use function assert; /** * Abstract base implementation of the {@see Doctrine\DBAL\Driver} interface for SQLite based drivers. */ abstract class AbstractSQLiteDriver implements Driver { /** * {@inheritDoc} */ public function getDatabasePlatform() { return new SqlitePlatform(); } /** * {@inheritDoc} * * @deprecated Use {@link SqlitePlatform::createSchemaManager()} instead. */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5458', 'AbstractSQLiteDriver::getSchemaManager() is deprecated.' . ' Use SqlitePlatform::createSchemaManager() instead.', ); assert($platform instanceof SqlitePlatform); return new SqliteSchemaManager($conn, $platform); } public function getExceptionConverter(): ExceptionConverter { return new SQLite\ExceptionConverter(); } } dbal/src/Driver/SQLite3/Connection.php 0000644 00000004612 15021222234 0013520 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\ParameterType; use SQLite3; use function assert; use function sprintf; final class Connection implements ServerInfoAwareConnection { private SQLite3 $connection; /** @internal The connection can be only instantiated by its driver. */ public function __construct(SQLite3 $connection) { $this->connection = $connection; } public function prepare(string $sql): Statement { try { $statement = $this->connection->prepare($sql); } catch (\Exception $e) { throw Exception::new($e); } assert($statement !== false); return new Statement($this->connection, $statement); } public function query(string $sql): Result { try { $result = $this->connection->query($sql); } catch (\Exception $e) { throw Exception::new($e); } assert($result !== false); return new Result($result, $this->connection->changes()); } /** @inheritDoc */ public function quote($value, $type = ParameterType::STRING): string { return sprintf('\'%s\'', SQLite3::escapeString($value)); } public function exec(string $sql): int { try { $this->connection->exec($sql); } catch (\Exception $e) { throw Exception::new($e); } return $this->connection->changes(); } /** @inheritDoc */ public function lastInsertId($name = null): int { return $this->connection->lastInsertRowID(); } public function beginTransaction(): bool { try { return $this->connection->exec('BEGIN'); } catch (\Exception $e) { return false; } } public function commit(): bool { try { return $this->connection->exec('COMMIT'); } catch (\Exception $e) { return false; } } public function rollBack(): bool { try { return $this->connection->exec('ROLLBACK'); } catch (\Exception $e) { return false; } } public function getNativeConnection(): SQLite3 { return $this->connection; } public function getServerVersion(): string { return SQLite3::version()['versionString']; } } dbal/src/Driver/SQLite3/Driver.php 0000644 00000002430 15021222234 0012650 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\AbstractSQLiteDriver; use Doctrine\DBAL\Driver\API\SQLite\UserDefinedFunctions; use SensitiveParameter; use SQLite3; final class Driver extends AbstractSQLiteDriver { /** * {@inheritDoc} */ public function connect( #[SensitiveParameter] array $params ): Connection { $isMemory = (bool) ($params['memory'] ?? false); if (isset($params['path'])) { if ($isMemory) { throw new Exception( 'Invalid connection settings: specifying both parameters "path" and "memory" is ambiguous.', ); } $filename = $params['path']; } elseif ($isMemory) { $filename = ':memory:'; } else { throw new Exception( 'Invalid connection settings: specify either the "path" or the "memory" parameter for SQLite3.', ); } try { $connection = new SQLite3($filename); } catch (\Exception $e) { throw Exception::new($e); } $connection->enableExceptions(true); UserDefinedFunctions::register([$connection, 'createFunction']); return new Connection($connection); } } dbal/src/Driver/SQLite3/Exception.php 0000644 00000000547 15021222234 0013362 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class Exception extends AbstractException { public static function new(\Exception $exception): self { return new self($exception->getMessage(), null, (int) $exception->getCode(), $exception); } } dbal/src/Driver/SQLite3/Result.php 0000644 00000003575 15021222234 0012706 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result as ResultInterface; use SQLite3Result; use const SQLITE3_ASSOC; use const SQLITE3_NUM; final class Result implements ResultInterface { private ?SQLite3Result $result; private int $changes; /** @internal The result can be only instantiated by its driver connection or statement. */ public function __construct(SQLite3Result $result, int $changes) { $this->result = $result; $this->changes = $changes; } /** @inheritDoc */ public function fetchNumeric() { if ($this->result === null) { return false; } return $this->result->fetchArray(SQLITE3_NUM); } /** @inheritDoc */ public function fetchAssociative() { if ($this->result === null) { return false; } return $this->result->fetchArray(SQLITE3_ASSOC); } /** @inheritDoc */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** @inheritDoc */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** @inheritDoc */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** @inheritDoc */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } public function rowCount(): int { return $this->changes; } public function columnCount(): int { if ($this->result === null) { return 0; } return $this->result->numColumns(); } public function free(): void { if ($this->result === null) { return; } $this->result->finalize(); $this->result = null; } } dbal/src/Driver/SQLite3/Statement.php 0000644 00000010031 15021222234 0013355 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLite3; use Doctrine\DBAL\Driver\Exception\UnknownParameterType; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use SQLite3; use SQLite3Stmt; use function assert; use function func_num_args; use function is_int; use const SQLITE3_BLOB; use const SQLITE3_INTEGER; use const SQLITE3_NULL; use const SQLITE3_TEXT; final class Statement implements StatementInterface { private const PARAM_TYPE_MAP = [ ParameterType::NULL => SQLITE3_NULL, ParameterType::INTEGER => SQLITE3_INTEGER, ParameterType::STRING => SQLITE3_TEXT, ParameterType::ASCII => SQLITE3_TEXT, ParameterType::BINARY => SQLITE3_BLOB, ParameterType::LARGE_OBJECT => SQLITE3_BLOB, ParameterType::BOOLEAN => SQLITE3_INTEGER, ]; private SQLite3 $connection; private SQLite3Stmt $statement; /** @internal The statement can be only instantiated by its driver connection. */ public function __construct(SQLite3 $connection, SQLite3Stmt $statement) { $this->connection = $connection; $this->statement = $statement; } /** * @throws UnknownParameterType * * {@inheritDoc} * * @psalm-assert ParameterType::* $type */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindValue() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->statement->bindValue($param, $value, $this->convertParamType($type)); } /** * @throws UnknownParameterType * * {@inheritDoc} * * @psalm-assert ParameterType::* $type */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } return $this->statement->bindParam($param, $variable, $this->convertParamType($type)); } /** @inheritDoc */ public function execute($params = null): Result { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); foreach ($params as $param => $value) { if (is_int($param)) { $this->bindValue($param + 1, $value, ParameterType::STRING); } else { $this->bindValue($param, $value, ParameterType::STRING); } } } try { $result = $this->statement->execute(); } catch (\Exception $e) { throw Exception::new($e); } assert($result !== false); return new Result($result, $this->connection->changes()); } /** * @psalm-return value-of<self::PARAM_TYPE_MAP> * * @psalm-assert ParameterType::* $type */ private function convertParamType(int $type): int { if (! isset(self::PARAM_TYPE_MAP[$type])) { throw UnknownParameterType::new($type); } return self::PARAM_TYPE_MAP[$type]; } } dbal/src/Driver/AbstractMySQLDriver.php 0000644 00000017614 15021222234 0014010 0 ustar 00 <?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(); } } dbal/src/Driver/ServerInfoAwareConnection.php 0000644 00000001037 15021222234 0015255 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; /** * Contract for a connection that is able to provide information about the server it is connected to. * * @deprecated The methods defined in this interface will be made part of the {@see Driver} interface * in the next major release. */ interface ServerInfoAwareConnection extends Connection { /** * Returns information about the version of the database server connected to. * * @return string * * @throws Exception */ public function getServerVersion(); } dbal/src/Driver/AbstractOracleDriver.php 0000644 00000003476 15021222234 0014251 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\AbstractOracleDriver\EasyConnectString; use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Driver\API\OCI; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Schema\OracleSchemaManager; use Doctrine\Deprecations\Deprecation; use function assert; /** * Abstract base implementation of the {@see Driver} interface for Oracle based drivers. */ abstract class AbstractOracleDriver implements Driver { /** * {@inheritDoc} */ public function getDatabasePlatform() { return new OraclePlatform(); } /** * {@inheritDoc} * * @deprecated Use {@link OraclePlatform::createSchemaManager()} instead. */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5458', 'AbstractOracleDriver::getSchemaManager() is deprecated.' . ' Use OraclePlatform::createSchemaManager() instead.', ); assert($platform instanceof OraclePlatform); return new OracleSchemaManager($conn, $platform); } public function getExceptionConverter(): ExceptionConverter { return new OCI\ExceptionConverter(); } /** * Returns an appropriate Easy Connect String for the given parameters. * * @param array<string, mixed> $params The connection parameters to return the Easy Connect String for. * * @return string */ protected function getEasyConnectString(array $params) { return (string) EasyConnectString::fromConnectionParameters($params); } } dbal/src/Driver/FetchUtils.php 0000644 00000002421 15021222234 0012243 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; /** @internal */ final class FetchUtils { /** * @return mixed|false * * @throws Exception */ public static function fetchOne(Result $result) { $row = $result->fetchNumeric(); if ($row === false) { return false; } return $row[0]; } /** * @return list<list<mixed>> * * @throws Exception */ public static function fetchAllNumeric(Result $result): array { $rows = []; while (($row = $result->fetchNumeric()) !== false) { $rows[] = $row; } return $rows; } /** * @return list<array<string,mixed>> * * @throws Exception */ public static function fetchAllAssociative(Result $result): array { $rows = []; while (($row = $result->fetchAssociative()) !== false) { $rows[] = $row; } return $rows; } /** * @return list<mixed> * * @throws Exception */ public static function fetchFirstColumn(Result $result): array { $rows = []; while (($row = $result->fetchOne()) !== false) { $rows[] = $row; } return $rows; } } dbal/src/Driver/PgSQL/Connection.php 0000644 00000010311 15021222234 0013213 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\SQL\Parser; use Doctrine\Deprecations\Deprecation; use PgSql\Connection as PgSqlConnection; use TypeError; use function assert; use function get_class; use function gettype; use function is_object; use function is_resource; use function pg_close; use function pg_escape_bytea; use function pg_escape_literal; use function pg_get_result; use function pg_last_error; use function pg_result_error; use function pg_send_prepare; use function pg_send_query; use function pg_version; use function sprintf; use function uniqid; final class Connection implements ServerInfoAwareConnection { /** @var PgSqlConnection|resource */ private $connection; private Parser $parser; /** @param PgSqlConnection|resource $connection */ public function __construct($connection) { if (! is_resource($connection) && ! $connection instanceof PgSqlConnection) { throw new TypeError(sprintf( 'Expected connection to be a resource or an instance of %s, got %s.', PgSqlConnection::class, is_object($connection) ? get_class($connection) : gettype($connection), )); } $this->connection = $connection; $this->parser = new Parser(false); } public function __destruct() { if (! isset($this->connection)) { return; } @pg_close($this->connection); } public function prepare(string $sql): Statement { $visitor = new ConvertParameters(); $this->parser->parse($sql, $visitor); $statementName = uniqid('dbal', true); if (@pg_send_prepare($this->connection, $statementName, $visitor->getSQL()) !== true) { throw new Exception(pg_last_error($this->connection)); } $result = @pg_get_result($this->connection); assert($result !== false); if ((bool) pg_result_error($result)) { throw Exception::fromResult($result); } return new Statement($this->connection, $statementName, $visitor->getParameterMap()); } public function query(string $sql): Result { if (@pg_send_query($this->connection, $sql) !== true) { throw new Exception(pg_last_error($this->connection)); } $result = @pg_get_result($this->connection); assert($result !== false); if ((bool) pg_result_error($result)) { throw Exception::fromResult($result); } return new Result($result); } /** {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { if ($type === ParameterType::BINARY || $type === ParameterType::LARGE_OBJECT) { return sprintf("'%s'", pg_escape_bytea($this->connection, $value)); } return pg_escape_literal($this->connection, $value); } public function exec(string $sql): int { return $this->query($sql)->rowCount(); } /** {@inheritDoc} */ public function lastInsertId($name = null) { if ($name !== null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4687', 'The usage of Connection::lastInsertId() with a sequence name is deprecated.', ); return $this->query(sprintf('SELECT CURRVAL(%s)', $this->quote($name)))->fetchOne(); } return $this->query('SELECT LASTVAL()')->fetchOne(); } /** @return true */ public function beginTransaction(): bool { $this->exec('BEGIN'); return true; } /** @return true */ public function commit(): bool { $this->exec('COMMIT'); return true; } /** @return true */ public function rollBack(): bool { $this->exec('ROLLBACK'); return true; } public function getServerVersion(): string { return (string) pg_version($this->connection)['server']; } /** @return PgSqlConnection|resource */ public function getNativeConnection() { return $this->connection; } } dbal/src/Driver/PgSQL/Driver.php 0000644 00000005040 15021222234 0012352 0 ustar 00 <?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), )); } } dbal/src/Driver/PgSQL/ConvertParameters.php 0000644 00000002223 15021222234 0014563 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\SQL\Parser\Visitor; use function count; use function implode; final class ConvertParameters implements Visitor { /** @var list<string> */ private array $buffer = []; /** @var array<array-key, int> */ private array $parameterMap = []; public function acceptPositionalParameter(string $sql): void { $position = count($this->parameterMap) + 1; $this->parameterMap[$position] = $position; $this->buffer[] = '$' . $position; } public function acceptNamedParameter(string $sql): void { $position = count($this->parameterMap) + 1; $this->parameterMap[$sql] = $position; $this->buffer[] = '$' . $position; } public function acceptOther(string $sql): void { $this->buffer[] = $sql; } public function getSQL(): string { return implode('', $this->buffer); } /** @return array<array-key, int> */ public function getParameterMap(): array { return $this->parameterMap; } } dbal/src/Driver/PgSQL/Exception.php 0000644 00000001265 15021222234 0013062 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\Driver\AbstractException; use PgSql\Result as PgSqlResult; use function pg_result_error_field; use const PGSQL_DIAG_MESSAGE_PRIMARY; use const PGSQL_DIAG_SQLSTATE; /** * @internal * * @psalm-immutable */ final class Exception extends AbstractException { /** @param PgSqlResult|resource $result */ public static function fromResult($result): self { $sqlstate = pg_result_error_field($result, PGSQL_DIAG_SQLSTATE); if ($sqlstate === false) { $sqlstate = null; } return new self((string) pg_result_error_field($result, PGSQL_DIAG_MESSAGE_PRIMARY), $sqlstate); } } dbal/src/Driver/PgSQL/Exception/UnexpectedValue.php 0000644 00000001137 15021222234 0016161 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PgSQL\Exception; use Doctrine\DBAL\Driver\Exception; use UnexpectedValueException; use function sprintf; /** @psalm-immutable */ final class UnexpectedValue extends UnexpectedValueException implements Exception { public static function new(string $value, string $type): self { return new self(sprintf( 'Unexpected value "%s" of type "%s" returned by Postgres', $value, $type, )); } /** @return null */ public function getSQLState() { return null; } } dbal/src/Driver/PgSQL/Exception/UnknownParameter.php 0000644 00000000604 15021222234 0016356 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PgSQL\Exception; use Doctrine\DBAL\Driver\AbstractException; use function sprintf; /** @psalm-immutable */ final class UnknownParameter extends AbstractException { public static function new(string $param): self { return new self( sprintf('Could not find parameter %s in the SQL statement', $param), ); } } dbal/src/Driver/PgSQL/Result.php 0000644 00000015266 15021222234 0012410 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\PgSQL\Exception\UnexpectedValue; use Doctrine\DBAL\Driver\Result as ResultInterface; use PgSql\Result as PgSqlResult; use TypeError; use function array_keys; use function array_map; use function assert; use function get_class; use function gettype; use function hex2bin; use function is_object; use function is_resource; use function pg_affected_rows; use function pg_fetch_all; use function pg_fetch_all_columns; use function pg_fetch_assoc; use function pg_fetch_row; use function pg_field_name; use function pg_field_type; use function pg_free_result; use function pg_num_fields; use function sprintf; use function substr; use const PGSQL_ASSOC; use const PGSQL_NUM; use const PHP_INT_SIZE; final class Result implements ResultInterface { /** @var PgSqlResult|resource|null */ private $result; /** @param PgSqlResult|resource $result */ public function __construct($result) { if (! is_resource($result) && ! $result instanceof PgSqlResult) { throw new TypeError(sprintf( 'Expected result to be a resource or an instance of %s, got %s.', PgSqlResult::class, is_object($result) ? get_class($result) : gettype($result), )); } $this->result = $result; } public function __destruct() { if (! isset($this->result)) { return; } $this->free(); } /** {@inheritDoc} */ public function fetchNumeric() { if ($this->result === null) { return false; } $row = pg_fetch_row($this->result); if ($row === false) { return false; } return $this->mapNumericRow($row, $this->fetchNumericColumnTypes()); } /** {@inheritDoc} */ public function fetchAssociative() { if ($this->result === null) { return false; } $row = pg_fetch_assoc($this->result); if ($row === false) { return false; } return $this->mapAssociativeRow($row, $this->fetchAssociativeColumnTypes()); } /** {@inheritDoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** {@inheritDoc} */ public function fetchAllNumeric(): array { if ($this->result === null) { return []; } $resultSet = pg_fetch_all($this->result, PGSQL_NUM); // On PHP 7.4, pg_fetch_all() might return false for empty result sets. if ($resultSet === false) { return []; } $types = $this->fetchNumericColumnTypes(); return array_map( fn (array $row) => $this->mapNumericRow($row, $types), $resultSet, ); } /** {@inheritDoc} */ public function fetchAllAssociative(): array { if ($this->result === null) { return []; } $resultSet = pg_fetch_all($this->result, PGSQL_ASSOC); // On PHP 7.4, pg_fetch_all() might return false for empty result sets. if ($resultSet === false) { return []; } $types = $this->fetchAssociativeColumnTypes(); return array_map( fn (array $row) => $this->mapAssociativeRow($row, $types), $resultSet, ); } /** {@inheritDoc} */ public function fetchFirstColumn(): array { if ($this->result === null) { return []; } $postgresType = pg_field_type($this->result, 0); return array_map( fn ($value) => $this->mapType($postgresType, $value), pg_fetch_all_columns($this->result), ); } public function rowCount(): int { if ($this->result === null) { return 0; } return pg_affected_rows($this->result); } public function columnCount(): int { if ($this->result === null) { return 0; } return pg_num_fields($this->result); } public function free(): void { if ($this->result === null) { return; } pg_free_result($this->result); $this->result = null; } /** @return array<int, string> */ private function fetchNumericColumnTypes(): array { assert($this->result !== null); $types = []; $numFields = pg_num_fields($this->result); for ($i = 0; $i < $numFields; ++$i) { $types[$i] = pg_field_type($this->result, $i); } return $types; } /** @return array<string, string> */ private function fetchAssociativeColumnTypes(): array { assert($this->result !== null); $types = []; $numFields = pg_num_fields($this->result); for ($i = 0; $i < $numFields; ++$i) { $types[pg_field_name($this->result, $i)] = pg_field_type($this->result, $i); } return $types; } /** * @param list<string|null> $row * @param array<int, string> $types * * @return list<mixed> */ private function mapNumericRow(array $row, array $types): array { assert($this->result !== null); return array_map( fn ($value, $field) => $this->mapType($types[$field], $value), $row, array_keys($row), ); } /** * @param array<string, string|null> $row * @param array<string, string> $types * * @return array<string, mixed> */ private function mapAssociativeRow(array $row, array $types): array { assert($this->result !== null); $mappedRow = []; foreach ($row as $field => $value) { $mappedRow[$field] = $this->mapType($types[$field], $value); } return $mappedRow; } /** @return string|int|float|bool|null */ private function mapType(string $postgresType, ?string $value) { if ($value === null) { return null; } switch ($postgresType) { case 'bool': switch ($value) { case 't': return true; case 'f': return false; } throw UnexpectedValue::new($value, $postgresType); case 'bytea': return hex2bin(substr($value, 2)); case 'float4': case 'float8': return (float) $value; case 'int2': case 'int4': return (int) $value; case 'int8': return PHP_INT_SIZE >= 8 ? (int) $value : $value; } return $value; } } dbal/src/Driver/PgSQL/Statement.php 0000644 00000012677 15021222234 0013101 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PgSQL; use Doctrine\DBAL\Driver\PgSQL\Exception\UnknownParameter; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use PgSql\Connection as PgSqlConnection; use TypeError; use function assert; use function func_num_args; use function get_class; use function gettype; use function is_int; use function is_object; use function is_resource; use function ksort; use function pg_escape_bytea; use function pg_escape_identifier; use function pg_get_result; use function pg_last_error; use function pg_query; use function pg_result_error; use function pg_send_execute; use function sprintf; use function stream_get_contents; final class Statement implements StatementInterface { /** @var PgSqlConnection|resource */ private $connection; private string $name; /** @var array<array-key, int> */ private array $parameterMap; /** @var array<int, mixed> */ private array $parameters = []; /** @psalm-var array<int, int> */ private array $parameterTypes = []; /** * @param PgSqlConnection|resource $connection * @param array<array-key, int> $parameterMap */ public function __construct($connection, string $name, array $parameterMap) { if (! is_resource($connection) && ! $connection instanceof PgSqlConnection) { throw new TypeError(sprintf( 'Expected connection to be a resource or an instance of %s, got %s.', PgSqlConnection::class, is_object($connection) ? get_class($connection) : gettype($connection), )); } $this->connection = $connection; $this->name = $name; $this->parameterMap = $parameterMap; } public function __destruct() { if (! isset($this->connection)) { return; } @pg_query( $this->connection, 'DEALLOCATE ' . pg_escape_identifier($this->connection, $this->name), ); } /** {@inheritDoc} */ public function bindValue($param, $value, $type = ParameterType::STRING): bool { if (! isset($this->parameterMap[$param])) { throw UnknownParameter::new((string) $param); } $this->parameters[$this->parameterMap[$param]] = $value; $this->parameterTypes[$this->parameterMap[$param]] = $type; return true; } /** {@inheritDoc} */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); if (func_num_args() < 3) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5558', 'Not passing $type to Statement::bindParam() is deprecated.' . ' Pass the type corresponding to the parameter being bound.', ); } if (func_num_args() > 4) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4533', 'The $driverOptions argument of Statement::bindParam() is deprecated.', ); } if (! isset($this->parameterMap[$param])) { throw UnknownParameter::new((string) $param); } $this->parameters[$this->parameterMap[$param]] = &$variable; $this->parameterTypes[$this->parameterMap[$param]] = $type; return true; } /** {@inheritDoc} */ public function execute($params = null): Result { if ($params !== null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::execute() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); foreach ($params as $param => $value) { if (is_int($param)) { $this->bindValue($param + 1, $value, ParameterType::STRING); } else { $this->bindValue($param, $value, ParameterType::STRING); } } } ksort($this->parameters); $escapedParameters = []; foreach ($this->parameters as $parameter => $value) { switch ($this->parameterTypes[$parameter]) { case ParameterType::BINARY: case ParameterType::LARGE_OBJECT: $escapedParameters[] = $value === null ? null : pg_escape_bytea( $this->connection, is_resource($value) ? stream_get_contents($value) : $value, ); break; default: $escapedParameters[] = $value; } } if (@pg_send_execute($this->connection, $this->name, $escapedParameters) !== true) { throw new Exception(pg_last_error($this->connection)); } $result = @pg_get_result($this->connection); assert($result !== false); if ((bool) pg_result_error($result)) { throw Exception::fromResult($result); } return new Result($result); } } dbal/src/Driver/Statement.php 0000644 00000007617 15021222234 0012151 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\ParameterType; /** * Driver-level statement */ interface Statement { /** * Binds a value to a corresponding named (not supported by mysqli driver, see comment below) or positional * placeholder in the SQL statement that was used to prepare the statement. * * As mentioned above, the named parameters are not natively supported by the mysqli driver, use executeQuery(), * fetchAll(), fetchArray(), fetchColumn(), fetchAssoc() methods to have the named parameter emulated by doctrine. * * @param string|int $param Parameter identifier. For a prepared statement using named placeholders, * this will be a parameter name of the form :name. For a prepared statement * using question mark placeholders, this will be the 1-indexed position of the parameter. * @param mixed $value The value to bind to the parameter. * @param int $type Explicit data type for the parameter using the {@see ParameterType} * constants. * * @return bool TRUE on success or FALSE on failure. * * @throws Exception */ public function bindValue($param, $value, $type = ParameterType::STRING); /** * Binds a PHP variable to a corresponding named (not supported by mysqli driver, see comment below) or question * mark placeholder in the SQL statement that was use to prepare the statement. Unlike {@see bindValue()}, * the variable is bound as a reference and will only be evaluated at the time * that PDOStatement->execute() is called. * * As mentioned above, the named parameters are not natively supported by the mysqli driver, use executeQuery(), * fetchAll(), fetchArray(), fetchColumn(), fetchAssoc() methods to have the named parameter emulated by doctrine. * * Most parameters are input parameters, that is, parameters that are * used in a read-only fashion to build up the query. Some drivers support the invocation * of stored procedures that return data as output parameters, and some also as input/output * parameters that both send in data and are updated to receive it. * * @deprecated Use {@see bindValue()} instead. * * @param string|int $param Parameter identifier. For a prepared statement using named placeholders, * this will be a parameter name of the form :name. For a prepared statement using * question mark placeholders, this will be the 1-indexed position of the parameter. * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. * @param int $type Explicit data type for the parameter using the {@see ParameterType} * constants. * @param int|null $length You must specify maxlength when using an OUT bind * so that PHP allocates enough memory to hold the returned value. * * @return bool TRUE on success or FALSE on failure. * * @throws Exception */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null); /** * Executes a prepared statement * * If the prepared statement included parameter markers, you must either: * call {@see bindParam()} to bind PHP variables to the parameter markers: * bound variables pass their value as input and receive the output value, * if any, of their associated parameter markers or pass an array of input-only * parameter values. * * @param mixed[]|null $params A numeric array of values with as many elements as there are * bound parameters in the SQL statement being executed. * * @throws Exception */ public function execute($params = null): Result; } dbal/src/Driver.php 0000644 00000003174 15021222234 0010177 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\Driver\Connection as DriverConnection; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; use SensitiveParameter; /** * Driver interface. * Interface that all DBAL drivers must implement. * * @psalm-import-type Params from DriverManager */ interface Driver { /** * Attempts to create a connection with the database. * * @param array<string, mixed> $params All connection parameters. * @psalm-param Params $params All connection parameters. * * @return DriverConnection The database connection. * * @throws Exception */ public function connect( #[SensitiveParameter] array $params ); /** * Gets the DatabasePlatform instance that provides all the metadata about * the platform this driver connects to. * * @return AbstractPlatform The database platform. */ public function getDatabasePlatform(); /** * Gets the SchemaManager that can be used to inspect and change the underlying * database schema of the platform this driver connects to. * * @deprecated Use {@link AbstractPlatform::createSchemaManager()} instead. * * @return AbstractSchemaManager */ public function getSchemaManager(Connection $conn, AbstractPlatform $platform); /** * Gets the ExceptionConverter that can be used to convert driver-level exceptions into DBAL exceptions. */ public function getExceptionConverter(): ExceptionConverter; } dbal/src/Id/TableGeneratorSchemaVisitor.php 0000644 00000003370 15021222234 0014675 0 ustar 00 <?php namespace Doctrine\DBAL\Id; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\Deprecations\Deprecation; /** @deprecated */ class TableGeneratorSchemaVisitor implements Visitor { /** @var string */ private $generatorTableName; /** @param string $generatorTableName */ public function __construct($generatorTableName = 'sequences') { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4681', 'The TableGeneratorSchemaVisitor class is is deprecated.', ); $this->generatorTableName = $generatorTableName; } /** * {@inheritDoc} */ public function acceptSchema(Schema $schema) { $table = $schema->createTable($this->generatorTableName); $table->addColumn('sequence_name', 'string'); $table->addColumn('sequence_value', 'integer', ['default' => 1]); $table->addColumn('sequence_increment_by', 'integer', ['default' => 1]); } /** * {@inheritDoc} */ public function acceptTable(Table $table) { } /** * {@inheritDoc} */ public function acceptColumn(Table $table, Column $column) { } /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { } /** * {@inheritDoc} */ public function acceptIndex(Table $table, Index $index) { } /** * {@inheritDoc} */ public function acceptSequence(Sequence $sequence) { } } dbal/src/Id/TableGenerator.php 0000644 00000012417 15021222234 0012176 0 ustar 00 <?php namespace Doctrine\DBAL\Id; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception; use Doctrine\Deprecations\Deprecation; use Throwable; use function array_change_key_case; use function assert; use function is_int; use const CASE_LOWER; /** * Table ID Generator for those poor languages that are missing sequences. * * WARNING: The Table Id Generator clones a second independent database * connection to work correctly. This means using the generator requests that * generate IDs will have two open database connections. This is necessary to * be safe from transaction failures in the main connection. Make sure to only * ever use one TableGenerator otherwise you end up with many connections. * * TableID Generator does not work with SQLite. * * The TableGenerator does not take care of creating the SQL Table itself. You * should look at the `TableGeneratorSchemaVisitor` to do this for you. * Otherwise the schema for a table looks like: * * CREATE sequences ( * sequence_name VARCHAR(255) NOT NULL, * sequence_value INT NOT NULL DEFAULT 1, * sequence_increment_by INT NOT NULL DEFAULT 1, * PRIMARY KEY (sequence_name) * ); * * Technically this generator works as follows: * * 1. Use a robust transaction serialization level. * 2. Open transaction * 3. Acquire a read lock on the table row (SELECT .. FOR UPDATE) * 4. Increment current value by one and write back to database * 5. Commit transaction * * If you are using a sequence_increment_by value that is larger than one the * ID Generator will keep incrementing values until it hits the incrementation * gap before issuing another query. * * If no row is present for a given sequence a new one will be created with the * default values 'value' = 1 and 'increment_by' = 1 * * @deprecated */ class TableGenerator { private Connection $conn; /** @var string */ private $generatorTableName; /** @var mixed[][] */ private array $sequences = []; /** * @param string $generatorTableName * * @throws Exception */ public function __construct(Connection $conn, $generatorTableName = 'sequences') { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4681', 'The TableGenerator class is is deprecated.', ); if ($conn->getDriver() instanceof Driver\PDO\SQLite\Driver) { throw new Exception('Cannot use TableGenerator with SQLite.'); } $this->conn = DriverManager::getConnection( $conn->getParams(), $conn->getConfiguration(), $conn->getEventManager(), ); $this->generatorTableName = $generatorTableName; } /** * Generates the next unused value for the given sequence name. * * @param string $sequence * * @return int * * @throws Exception */ public function nextValue($sequence) { if (isset($this->sequences[$sequence])) { $value = $this->sequences[$sequence]['value']; $this->sequences[$sequence]['value']++; if ($this->sequences[$sequence]['value'] >= $this->sequences[$sequence]['max']) { unset($this->sequences[$sequence]); } return $value; } $this->conn->beginTransaction(); try { $row = $this->conn->createQueryBuilder() ->select('sequence_value', 'sequence_increment_by') ->from($this->generatorTableName) ->where('sequence_name = ?') ->forUpdate() ->setParameter(1, $sequence) ->fetchAssociative(); if ($row !== false) { $row = array_change_key_case($row, CASE_LOWER); $value = $row['sequence_value']; $value++; assert(is_int($value)); if ($row['sequence_increment_by'] > 1) { $this->sequences[$sequence] = [ 'value' => $value, 'max' => $row['sequence_value'] + $row['sequence_increment_by'], ]; } $sql = 'UPDATE ' . $this->generatorTableName . ' ' . 'SET sequence_value = sequence_value + sequence_increment_by ' . 'WHERE sequence_name = ? AND sequence_value = ?'; $rows = $this->conn->executeStatement($sql, [$sequence, $row['sequence_value']]); if ($rows !== 1) { throw new Exception('Race-condition detected while updating sequence. Aborting generation'); } } else { $this->conn->insert( $this->generatorTableName, ['sequence_name' => $sequence, 'sequence_value' => 1, 'sequence_increment_by' => 1], ); $value = 1; } $this->conn->commit(); } catch (Throwable $e) { $this->conn->rollBack(); throw new Exception( 'Error occurred while generating ID with TableGenerator, aborted generation: ' . $e->getMessage(), 0, $e, ); } return $value; } } dbal/src/FetchMode.php 0000644 00000000646 15021222234 0010603 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Legacy Class that keeps BC for using the legacy APIs fetch()/fetchAll(). * * @deprecated Use the dedicated fetch*() methods for the desired fetch mode instead. */ class FetchMode { /** @link PDO::FETCH_ASSOC */ public const ASSOCIATIVE = 2; /** @link PDO::FETCH_NUM */ public const NUMERIC = 3; /** @link PDO::FETCH_COLUMN */ public const COLUMN = 7; } dbal/src/Exception.php 0000644 00000011713 15021222234 0010700 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; use SensitiveParameter; use function get_class; use function gettype; use function implode; use function is_object; use function spl_object_hash; use function sprintf; /** @psalm-immutable */ class Exception extends \Exception { public static function notSupported(string $method): self { return new self(sprintf("Operation '%s' is not supported by platform.", $method)); } /** @param mixed $invalidPlatform */ public static function invalidPlatformType($invalidPlatform): self { if (is_object($invalidPlatform)) { return new self( sprintf( "Option 'platform' must be a subtype of '%s', instance of '%s' given", AbstractPlatform::class, get_class($invalidPlatform), ), ); } return new self( sprintf( "Option 'platform' must be an object and subtype of '%s'. Got '%s'", AbstractPlatform::class, gettype($invalidPlatform), ), ); } /** * Returns a new instance for an invalid specified platform version. * * @param string $version The invalid platform version given. * @param string $expectedFormat The expected platform version format. */ public static function invalidPlatformVersionSpecified(string $version, string $expectedFormat): self { return new self( sprintf( 'Invalid platform version "%s" specified. ' . 'The platform version has to be specified in the format: "%s".', $version, $expectedFormat, ), ); } /** @param string|null $url The URL that was provided in the connection parameters (if any). */ public static function driverRequired( #[SensitiveParameter] ?string $url = null ): self { if ($url !== null) { return new self( sprintf( "The options 'driver' or 'driverClass' are mandatory if a connection URL without scheme " . 'is given to DriverManager::getConnection(). Given URL: %s', $url, ), ); } return new self("The options 'driver' or 'driverClass' are mandatory if no PDO " . 'instance is given to DriverManager::getConnection().'); } /** @param string[] $knownDrivers */ public static function unknownDriver(string $unknownDriverName, array $knownDrivers): self { return new self("The given 'driver' " . $unknownDriverName . ' is unknown, ' . 'Doctrine currently supports only the following drivers: ' . implode(', ', $knownDrivers)); } public static function invalidWrapperClass(string $wrapperClass): self { return new self("The given 'wrapperClass' " . $wrapperClass . ' has to be a ' . 'subtype of \Doctrine\DBAL\Connection.'); } public static function invalidDriverClass(string $driverClass): self { return new self( "The given 'driverClass' " . $driverClass . ' has to implement the ' . Driver::class . ' interface.', ); } public static function noColumnsSpecifiedForTable(string $tableName): self { return new self('No columns specified for table ' . $tableName); } public static function typeExists(string $name): self { return new self('Type ' . $name . ' already exists.'); } public static function unknownColumnType(string $name): self { return new self('Unknown column type "' . $name . '" requested. Any Doctrine type that you use has ' . 'to be registered with \Doctrine\DBAL\Types\Type::addType(). You can get a list of all the ' . 'known types with \Doctrine\DBAL\Types\Type::getTypesMap(). If this error occurs during database ' . 'introspection then you might have forgotten to register all database types for a Doctrine Type. Use ' . 'AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement ' . 'Type#getMappedDatabaseTypes(). If the type name is empty you might ' . 'have a problem with the cache or forgot some mapping information.'); } public static function typeNotFound(string $name): self { return new self('Type to be overwritten ' . $name . ' does not exist.'); } public static function typeNotRegistered(Type $type): self { return new self( sprintf('Type of the class %s@%s is not registered.', get_class($type), spl_object_hash($type)), ); } public static function typeAlreadyRegistered(Type $type): self { return new self( sprintf('Type of the class %s@%s is already registered.', get_class($type), spl_object_hash($type)), ); } } dbal/src/Exception/DatabaseDoesNotExist.php 0000644 00000000212 15021222234 0014705 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** @psalm-immutable */ class DatabaseDoesNotExist extends DatabaseObjectNotFoundException { } dbal/src/Exception/DriverException.php 0000644 00000002646 15021222234 0014017 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Driver\Exception as TheDriverException; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Query; use function assert; /** * Base class for all errors detected in the driver. * * @psalm-immutable */ class DriverException extends Exception implements TheDriverException { /** * The query that triggered the exception, if any. */ private ?Query $query; /** * @internal * * @param TheDriverException $driverException The DBAL driver exception to chain. * @param Query|null $query The SQL query that triggered the exception, if any. */ public function __construct(TheDriverException $driverException, ?Query $query) { if ($query !== null) { $message = 'An exception occurred while executing a query: ' . $driverException->getMessage(); } else { $message = 'An exception occurred in the driver: ' . $driverException->getMessage(); } parent::__construct($message, $driverException->getCode(), $driverException); $this->query = $query; } /** * {@inheritDoc} */ public function getSQLState() { $previous = $this->getPrevious(); assert($previous instanceof TheDriverException); return $previous->getSQLState(); } public function getQuery(): ?Query { return $this->query; } } dbal/src/Exception/SchemaDoesNotExist.php 0000644 00000000210 15021222234 0014377 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** @psalm-immutable */ class SchemaDoesNotExist extends DatabaseObjectNotFoundException { } dbal/src/Exception/DeadlockException.php 0000644 00000000347 15021222234 0014266 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a deadlock error of a transaction detected in the driver. * * @psalm-immutable */ class DeadlockException extends ServerException implements RetryableException { } dbal/src/Exception/DatabaseRequired.php 0000644 00000000541 15021222234 0014102 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception; use function sprintf; /** @psalm-immutable */ class DatabaseRequired extends Exception { public static function new(string $methodName): self { return new self(sprintf('A database is required for the method: %s.', $methodName)); } } dbal/src/Exception/NoKeyValue.php 0000644 00000000732 15021222234 0012721 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception; use function sprintf; /** * @internal * * @psalm-immutable */ final class NoKeyValue extends Exception { public static function fromColumnCount(int $columnCount): self { return new self( sprintf( 'Fetching as key-value pairs requires the result to contain at least 2 columns, %d given.', $columnCount, ), ); } } dbal/src/Exception/DatabaseObjectNotFoundException.php 0000644 00000000647 15021222234 0017073 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all unknown database object related errors detected in the driver. * * A database object is considered any asset that can be created in a database * such as schemas, tables, views, sequences, triggers, constraints, indexes, * functions, stored procedures etc. * * @psalm-immutable */ class DatabaseObjectNotFoundException extends ServerException { } dbal/src/Exception/RetryableException.php 0000644 00000000340 15021222234 0014502 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Throwable; /** * Marker interface for all exceptions where retrying the transaction makes sense. * * @psalm-immutable */ interface RetryableException extends Throwable { } dbal/src/Exception/InvalidLockMode.php 0000644 00000001013 15021222234 0013674 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception; use function sprintf; /** @psalm-immutable */ class InvalidLockMode extends Exception { public static function fromLockMode(int $lockMode): self { return new self( sprintf( 'Lock mode %d is invalid. The valid values are LockMode::NONE, LockMode::OPTIMISTIC' . ', LockMode::PESSIMISTIC_READ and LockMode::PESSIMISTIC_WRITE', $lockMode, ), ); } } dbal/src/Exception/InvalidArgumentException.php 0000644 00000000615 15021222234 0015647 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception; /** * Exception to be thrown when invalid arguments are passed to any DBAL API * * @psalm-immutable */ class InvalidArgumentException extends Exception { /** @return self */ public static function fromEmptyCriteria() { return new self('Empty criteria was used, expected non-empty criteria'); } } dbal/src/Exception/InvalidFieldNameException.php 0000644 00000000336 15021222234 0015711 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for an invalid specified field name in a statement detected in the driver. * * @psalm-immutable */ class InvalidFieldNameException extends ServerException { } dbal/src/Exception/NonUniqueFieldNameException.php 0000644 00000000354 15021222234 0016244 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a non-unique/ambiguous specified field name in a statement detected in the driver. * * @psalm-immutable */ class NonUniqueFieldNameException extends ServerException { } dbal/src/Exception/NotNullConstraintViolationException.php 0000644 00000000346 15021222234 0020104 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a NOT NULL constraint violation detected in the driver. * * @psalm-immutable */ class NotNullConstraintViolationException extends ConstraintViolationException { } dbal/src/Exception/ServerException.php 0000644 00000000300 15021222234 0014013 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all server related errors detected in the driver. * * @psalm-immutable */ class ServerException extends DriverException { } dbal/src/Exception/ConnectionLost.php 0000644 00000000176 15021222234 0013642 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** @psalm-immutable */ final class ConnectionLost extends ConnectionException { } dbal/src/Exception/DatabaseObjectExistsException.php 0000644 00000000656 15021222234 0016616 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all already existing database object related errors detected in the driver. * * A database object is considered any asset that can be created in a database * such as schemas, tables, views, sequences, triggers, constraints, indexes, * functions, stored procedures etc. * * @psalm-immutable */ class DatabaseObjectExistsException extends ServerException { } dbal/src/Exception/UniqueConstraintViolationException.php 0000644 00000000343 15021222234 0017754 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a unique constraint violation detected in the driver. * * @psalm-immutable */ class UniqueConstraintViolationException extends ConstraintViolationException { } dbal/src/Exception/ConnectionException.php 0000644 00000000310 15021222234 0014645 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all connection related errors detected in the driver. * * @psalm-immutable */ class ConnectionException extends DriverException { } dbal/src/Exception/SyntaxErrorException.php 0000644 00000000310 15021222234 0015046 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a syntax error in a statement detected in the driver. * * @psalm-immutable */ class SyntaxErrorException extends ServerException { } dbal/src/Exception/LockWaitTimeoutException.php 0000644 00000000367 15021222234 0015646 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a lock wait timeout error of a transaction detected in the driver. * * @psalm-immutable */ class LockWaitTimeoutException extends ServerException implements RetryableException { } dbal/src/Exception/ForeignKeyConstraintViolationException.php 0000644 00000000354 15021222234 0020552 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a foreign key constraint violation detected in the driver. * * @psalm-immutable */ class ForeignKeyConstraintViolationException extends ConstraintViolationException { } dbal/src/Exception/ReadOnlyException.php 0000644 00000000341 15021222234 0014267 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a write operation attempt on a read-only database element detected in the driver. * * @psalm-immutable */ class ReadOnlyException extends ServerException { } dbal/src/Exception/MalformedDsnException.php 0000644 00000000424 15021222234 0015127 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use InvalidArgumentException; /** @psalm-immutable */ class MalformedDsnException extends InvalidArgumentException { public static function new(): self { return new self('Malformed database connection URL'); } } dbal/src/Exception/ConstraintViolationException.php 0000644 00000000333 15021222234 0016564 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all constraint violation related errors detected in the driver. * * @psalm-immutable */ class ConstraintViolationException extends ServerException { } dbal/src/Exception/TableNotFoundException.php 0000644 00000000347 15021222234 0015264 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for an unknown table referenced in a statement detected in the driver. * * @psalm-immutable */ class TableNotFoundException extends DatabaseObjectNotFoundException { } dbal/src/Exception/TableExistsException.php 0000644 00000000354 15021222234 0015005 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for an already existing table referenced in a statement detected in the driver. * * @psalm-immutable */ class TableExistsException extends DatabaseObjectExistsException { } dbal/src/ArrayParameterType.php 0000644 00000002204 15021222234 0012516 0 ustar 00 <?php namespace Doctrine\DBAL; final class ArrayParameterType { /** * Represents an array of ints to be expanded by Doctrine SQL parsing. */ public const INTEGER = ParameterType::INTEGER + Connection::ARRAY_PARAM_OFFSET; /** * Represents an array of strings to be expanded by Doctrine SQL parsing. */ public const STRING = ParameterType::STRING + Connection::ARRAY_PARAM_OFFSET; /** * Represents an array of ascii strings to be expanded by Doctrine SQL parsing. */ public const ASCII = ParameterType::ASCII + Connection::ARRAY_PARAM_OFFSET; /** * Represents an array of ascii strings to be expanded by Doctrine SQL parsing. */ public const BINARY = ParameterType::BINARY + Connection::ARRAY_PARAM_OFFSET; /** * @internal * * @psalm-param self::* $type * * @psalm-return ParameterType::INTEGER|ParameterType::STRING|ParameterType::ASCII|ParameterType::BINARY */ public static function toElementParameterType(int $type): int { return $type - Connection::ARRAY_PARAM_OFFSET; } private function __construct() { } } dbal/src/Types/BooleanType.php 0000644 00000003340 15021222234 0012264 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\Deprecations\Deprecation; /** * Type that maps an SQL boolean to a PHP boolean. */ class BooleanType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBooleanTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return $platform->convertBooleansToDatabaseValue($value); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : bool) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $platform->convertFromBoolean($value); } /** * {@inheritDoc} */ public function getName() { return Types::BOOLEAN; } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::BOOLEAN; } /** * @deprecated * * @return bool */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); // We require a commented boolean type in order to distinguish between // boolean and smallint as both (have to) map to the same native type. return $platform instanceof DB2Platform; } } dbal/src/Types/JsonType.php 0000644 00000004222 15021222234 0011616 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use JsonException; use function is_resource; use function json_decode; use function json_encode; use function stream_get_contents; use const JSON_PRESERVE_ZERO_FRACTION; use const JSON_THROW_ON_ERROR; /** * Type generating json objects values */ class JsonType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getJsonTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } try { return json_encode($value, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION); } catch (JsonException $e) { throw ConversionException::conversionFailedSerialization($value, 'json', $e->getMessage(), $e); } } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value === '') { return null; } if (is_resource($value)) { $value = stream_get_contents($value); } try { return json_decode($value, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { throw ConversionException::conversionFailed($value, $this->getName(), $e); } } /** * {@inheritDoc} */ public function getName() { return Types::JSON; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return ! $platform->hasNativeJsonType(); } } dbal/src/Types/ArrayType.php 0000644 00000004066 15021222234 0011771 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function is_resource; use function restore_error_handler; use function serialize; use function set_error_handler; use function stream_get_contents; use function unserialize; use const E_DEPRECATED; use const E_USER_DEPRECATED; /** * Type that maps a PHP array to a clob SQL type. * * @deprecated Use {@link JsonType} instead. */ class ArrayType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { // @todo 3.0 - $value === null check to save real NULL in database return serialize($value); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $value = is_resource($value) ? stream_get_contents($value) : $value; set_error_handler(function (int $code, string $message): bool { if ($code === E_DEPRECATED || $code === E_USER_DEPRECATED) { return false; } throw ConversionException::conversionFailedUnserialization($this->getName(), $message); }); try { return unserialize($value); } finally { restore_error_handler(); } } /** * {@inheritDoc} */ public function getName() { return Types::ARRAY; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } dbal/src/Types/StringType.php 0000644 00000000742 15021222234 0012156 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL VARCHAR to a PHP string. */ class StringType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getStringTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getName() { return Types::STRING; } } dbal/src/Types/AsciiStringType.php 0000644 00000001106 15021222234 0013122 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; final class AsciiStringType extends StringType { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { return $platform->getAsciiStringTypeDeclarationSQL($column); } public function getBindingType(): int { return ParameterType::ASCII; } public function getName(): string { return Types::ASCII_STRING; } } dbal/src/Types/BinaryType.php 0000644 00000002503 15021222234 0012131 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use function assert; use function fopen; use function fseek; use function fwrite; use function is_resource; use function is_string; /** * Type that maps ab SQL BINARY/VARBINARY to a PHP resource stream. */ class BinaryType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBinaryTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } if (is_string($value)) { $fp = fopen('php://temp', 'rb+'); assert(is_resource($fp)); fwrite($fp, $value); fseek($fp, 0); $value = $fp; } if (! is_resource($value)) { throw ConversionException::conversionFailed($value, Types::BINARY); } return $value; } /** * {@inheritDoc} */ public function getName() { return Types::BINARY; } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::BINARY; } } dbal/src/Types/DateImmutableType.php 0000644 00000004002 15021222234 0013416 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; /** * Immutable type of {@see DateType}. */ class DateImmutableType extends DateType { /** * {@inheritDoc} */ public function getName() { return Types::DATE_IMMUTABLE; } /** * {@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) { return $value->format($platform->getDateFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeImmutable) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat('!' . $platform->getDateFormatString(), $value); if ($dateTime === false) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateFormatString(), ); } return $dateTime; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } dbal/src/Types/ConversionException.php 0000644 00000007157 15021222234 0014061 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Exception; use Throwable; use function func_get_arg; use function func_num_args; use function get_class; use function gettype; use function implode; use function is_object; use function is_scalar; use function sprintf; use function strlen; use function substr; use function var_export; /** * Conversion Exception is thrown when the database to PHP conversion fails. * * @psalm-immutable */ class ConversionException extends Exception { /** * Thrown when a Database to Doctrine Type Conversion fails. * * @param mixed $value * @param string $toType * * @return ConversionException */ public static function conversionFailed($value, $toType, ?Throwable $previous = null) { $value = strlen($value) > 32 ? substr($value, 0, 20) . '...' : $value; return new self('Could not convert database value "' . $value . '" to Doctrine Type ' . $toType, 0, $previous); } /** * Thrown when a Database to Doctrine Type Conversion fails and we can make a statement * about the expected format. * * @param mixed $value * @param string $toType * @param string $expectedFormat * * @return ConversionException */ public static function conversionFailedFormat($value, $toType, $expectedFormat, ?Throwable $previous = null) { $value = strlen($value) > 32 ? substr($value, 0, 20) . '...' : $value; return new self( 'Could not convert database value "' . $value . '" to Doctrine Type ' . $toType . '. Expected format: ' . $expectedFormat, 0, $previous, ); } /** * Thrown when the PHP value passed to the converter was not of the expected type. * * @param mixed $value * @param string $toType * @param string[] $possibleTypes * * @return ConversionException */ public static function conversionFailedInvalidType( $value, $toType, array $possibleTypes, ?Throwable $previous = null ) { if (is_scalar($value) || $value === null) { return new self(sprintf( 'Could not convert PHP value %s to type %s. Expected one of the following types: %s', var_export($value, true), $toType, implode(', ', $possibleTypes), ), 0, $previous); } return new self(sprintf( 'Could not convert PHP value of type %s to type %s. Expected one of the following types: %s', is_object($value) ? get_class($value) : gettype($value), $toType, implode(', ', $possibleTypes), ), 0, $previous); } /** * @param mixed $value * @param string $format * @param string $error * * @return ConversionException */ public static function conversionFailedSerialization($value, $format, $error /*, ?Throwable $previous = null */) { $actualType = is_object($value) ? get_class($value) : gettype($value); return new self(sprintf( "Could not convert PHP type '%s' to '%s', as an '%s' error was triggered by the serialization", $actualType, $format, $error, ), 0, func_num_args() >= 4 ? func_get_arg(3) : null); } public static function conversionFailedUnserialization(string $format, string $error): self { return new self(sprintf( "Could not convert database value to '%s' as an error was triggered by the unserialization: '%s'", $format, $error, )); } } dbal/src/Types/SimpleArrayType.php 0000644 00000003450 15021222234 0013137 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function count; use function explode; use function implode; use function is_array; use function is_resource; use function stream_get_contents; /** * Array Type which can be used for simple values. * * Only use this type if you are sure that your values cannot contain a ",". */ class SimpleArrayType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param mixed $value * * @return string|null */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if (! is_array($value) || count($value) === 0) { return null; } return implode(',', $value); } /** * {@inheritDoc} * * @param mixed $value * * @return list<string> */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return []; } $value = is_resource($value) ? stream_get_contents($value) : $value; return explode(',', $value); } /** * {@inheritDoc} */ public function getName() { return Types::SIMPLE_ARRAY; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } dbal/src/Types/TypeRegistry.php 0000644 00000006276 15021222234 0012530 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Exception; use function spl_object_id; /** * The type registry is responsible for holding a map of all known DBAL types. * The types are stored using the flyweight pattern so that one type only exists as exactly one instance. */ final class TypeRegistry { /** @var array<string, Type> Map of type names and their corresponding flyweight objects. */ private array $instances; /** @var array<int, string> */ private array $instancesReverseIndex; /** @param array<string, Type> $instances */ public function __construct(array $instances = []) { $this->instances = []; $this->instancesReverseIndex = []; foreach ($instances as $name => $type) { $this->register($name, $type); } } /** * Finds a type by the given name. * * @throws Exception */ public function get(string $name): Type { $type = $this->instances[$name] ?? null; if ($type === null) { throw Exception::unknownColumnType($name); } return $type; } /** * Finds a name for the given type. * * @throws Exception */ public function lookupName(Type $type): string { $name = $this->findTypeName($type); if ($name === null) { throw Exception::typeNotRegistered($type); } return $name; } /** * Checks if there is a type of the given name. */ public function has(string $name): bool { return isset($this->instances[$name]); } /** * Registers a custom type to the type map. * * @throws Exception */ public function register(string $name, Type $type): void { if (isset($this->instances[$name])) { throw Exception::typeExists($name); } if ($this->findTypeName($type) !== null) { throw Exception::typeAlreadyRegistered($type); } $this->instances[$name] = $type; $this->instancesReverseIndex[spl_object_id($type)] = $name; } /** * Overrides an already defined type to use a different implementation. * * @throws Exception */ public function override(string $name, Type $type): void { $origType = $this->instances[$name] ?? null; if ($origType === null) { throw Exception::typeNotFound($name); } if (($this->findTypeName($type) ?? $name) !== $name) { throw Exception::typeAlreadyRegistered($type); } unset($this->instancesReverseIndex[spl_object_id($origType)]); $this->instances[$name] = $type; $this->instancesReverseIndex[spl_object_id($type)] = $name; } /** * Gets the map of all registered types and their corresponding type instances. * * @internal * * @return array<string, Type> */ public function getMap(): array { return $this->instances; } private function findTypeName(Type $type): ?string { return $this->instancesReverseIndex[spl_object_id($type)] ?? null; } } dbal/src/Types/BlobType.php 0000644 00000002465 15021222234 0011572 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use function assert; use function fopen; use function fseek; use function fwrite; use function is_resource; use function is_string; /** * Type that maps an SQL BLOB to a PHP resource stream. */ class BlobType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBlobTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } if (is_string($value)) { $fp = fopen('php://temp', 'rb+'); assert(is_resource($fp)); fwrite($fp, $value); fseek($fp, 0); $value = $fp; } if (! is_resource($value)) { throw ConversionException::conversionFailed($value, Types::BLOB); } return $value; } /** * {@inheritDoc} */ public function getName() { return Types::BLOB; } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::LARGE_OBJECT; } } dbal/src/Types/PhpDateTimeMappingType.php 0000644 00000000267 15021222234 0014372 0 ustar 00 <?php namespace Doctrine\DBAL\Types; /** * Implementations should map a database type to a PHP DateTimeInterface instance. * * @internal */ interface PhpDateTimeMappingType { } dbal/src/Types/DateType.php 0000644 00000005243 15021222234 0011566 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeImmutable; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function get_class; /** * Type that maps an SQL DATE to a PHP Date object. */ class DateType extends Type { /** * {@inheritDoc} */ public function getName() { return Types::DATE_MUTABLE; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getDateTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @psalm-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 DateTimeInterface) { 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), DateImmutableType::class, __FUNCTION__, ); } return $value->format($platform->getDateFormatString()); } 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), DateImmutableType::class, __FUNCTION__, ); } if ($value === null || $value instanceof DateTimeInterface) { return $value; } $dateTime = DateTime::createFromFormat('!' . $platform->getDateFormatString(), $value); if ($dateTime !== false) { return $dateTime; } throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateFormatString(), ); } } dbal/src/Types/ObjectType.php 0000644 00000003601 15021222234 0012113 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function is_resource; use function restore_error_handler; use function serialize; use function set_error_handler; use function stream_get_contents; use function unserialize; /** * Type that maps a PHP object to a clob SQL type. * * @deprecated Use {@link JsonType} instead. */ class ObjectType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param mixed $value * * @return string */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return serialize($value); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $value = is_resource($value) ? stream_get_contents($value) : $value; set_error_handler(function (int $code, string $message): bool { throw ConversionException::conversionFailedUnserialization($this->getName(), $message); }); try { return unserialize($value); } finally { restore_error_handler(); } } /** * {@inheritDoc} */ public function getName() { return Types::OBJECT; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } dbal/src/Types/BigIntType.php 0000644 00000001703 15021222234 0012062 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps a database BIGINT to a PHP string. */ class BigIntType extends Type implements PhpIntegerMappingType { /** * {@inheritDoc} */ public function getName() { return Types::BIGINT; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBigIntTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::STRING; } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (string) $value; } } dbal/src/Types/DecimalType.php 0000644 00000002045 15021222234 0012244 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use function is_float; use function is_int; use const PHP_VERSION_ID; /** * Type that maps an SQL DECIMAL to a PHP string. */ class DecimalType extends Type { /** * {@inheritDoc} */ public function getName() { return Types::DECIMAL; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getDecimalTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { // Some drivers starting from PHP 8.1 can represent decimals as float/int // See also: https://github.com/doctrine/dbal/pull/4818 if ((PHP_VERSION_ID >= 80100 || $platform instanceof SqlitePlatform) && (is_float($value) || is_int($value))) { return (string) $value; } return $value; } } dbal/src/Types/Types.php 0000644 00000003060 15021222234 0011146 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Types; /** * Default built-in types provided by Doctrine DBAL. */ final class Types { /** @deprecated Use {@link Types::JSON} instead. */ public const ARRAY = 'array'; public const ASCII_STRING = 'ascii_string'; public const BIGINT = 'bigint'; public const BINARY = 'binary'; public const BLOB = 'blob'; public const BOOLEAN = 'boolean'; public const DATE_MUTABLE = 'date'; public const DATE_IMMUTABLE = 'date_immutable'; public const DATEINTERVAL = 'dateinterval'; public const DATETIME_MUTABLE = 'datetime'; public const DATETIME_IMMUTABLE = 'datetime_immutable'; public const DATETIMETZ_MUTABLE = 'datetimetz'; public const DATETIMETZ_IMMUTABLE = 'datetimetz_immutable'; public const DECIMAL = 'decimal'; public const FLOAT = 'float'; public const GUID = 'guid'; public const INTEGER = 'integer'; public const JSON = 'json'; /** @deprecated Use {@link Types::JSON} instead. */ public const OBJECT = 'object'; public const SIMPLE_ARRAY = 'simple_array'; public const SMALLINT = 'smallint'; public const STRING = 'string'; public const TEXT = 'text'; public const TIME_MUTABLE = 'time'; public const TIME_IMMUTABLE = 'time_immutable'; /** @codeCoverageIgnore */ private function __construct() { } } dbal/src/Types/DateTimeTzImmutableType.php 0000644 00000004034 15021222234 0014560 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; /** * Immutable type of {@see DateTimeTzType}. */ class DateTimeTzImmutableType extends DateTimeTzType { /** * {@inheritDoc} */ public function getName() { return Types::DATETIMETZ_IMMUTABLE; } /** * {@inheritDoc} * * @psalm-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) { return $value->format($platform->getDateTimeTzFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeImmutable) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat($platform->getDateTimeTzFormatString(), $value); if ($dateTime !== false) { return $dateTime; } throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateTimeTzFormatString(), ); } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } dbal/src/Types/IntegerType.php 0000644 00000001673 15021222234 0012311 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL INT to a PHP integer. */ class IntegerType extends Type implements PhpIntegerMappingType { /** * {@inheritDoc} */ public function getName() { return Types::INTEGER; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : int) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (int) $value; } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::INTEGER; } } dbal/src/Types/DateTimeType.php 0000644 00000005646 15021222234 0012414 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeImmutable; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use Exception; use function get_class; /** * Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object. */ class DateTimeType extends Type implements PhpDateTimeMappingType { /** * {@inheritDoc} */ public function getName() { return Types::DATETIME_MUTABLE; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getDateTimeTypeDeclarationSQL($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), DateTimeImmutableType::class, __FUNCTION__, ); } if ($value instanceof DateTimeInterface) { return $value->format($platform->getDateTimeFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTime::class, DateTimeImmutable::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), DateTimeImmutableType::class, __FUNCTION__, ); } if ($value === null || $value instanceof DateTimeInterface) { return $value; } $dateTime = DateTime::createFromFormat($platform->getDateTimeFormatString(), $value); if ($dateTime !== false) { return $dateTime; } try { return new DateTime($value); } catch (Exception $e) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateTimeFormatString(), $e, ); } } } dbal/src/Types/PhpIntegerMappingType.php 0000644 00000000243 15021222234 0014265 0 ustar 00 <?php namespace Doctrine\DBAL\Types; /** * Implementations should map a database type to a PHP integer. * * @internal */ interface PhpIntegerMappingType { } dbal/src/Types/GuidType.php 0000644 00000001671 15021222234 0011602 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; /** * Represents a GUID/UUID datatype (both are actually synonyms) in the database. */ class GuidType extends StringType { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getGuidTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getName() { return Types::GUID; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return ! $platform->hasNativeGuidType(); } } dbal/src/Types/SmallIntType.php 0000644 00000001707 15021222234 0012435 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps a database SMALLINT to a PHP integer. */ class SmallIntType extends Type implements PhpIntegerMappingType { /** * {@inheritDoc} */ public function getName() { return Types::SMALLINT; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getSmallIntTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : int) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (int) $value; } /** * {@inheritDoc} */ public function getBindingType() { return ParameterType::INTEGER; } } dbal/src/Types/Type.php 0000644 00000021163 15021222234 0010767 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Exception; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function array_map; use function get_class; /** * The base class for so-called Doctrine mapping types. * * A Type object is obtained by calling the static {@see getType()} method. */ abstract class Type { /** * The map of supported doctrine mapping types. */ private const BUILTIN_TYPES_MAP = [ Types::ARRAY => ArrayType::class, Types::ASCII_STRING => AsciiStringType::class, Types::BIGINT => BigIntType::class, Types::BINARY => BinaryType::class, Types::BLOB => BlobType::class, Types::BOOLEAN => BooleanType::class, Types::DATE_MUTABLE => DateType::class, Types::DATE_IMMUTABLE => DateImmutableType::class, Types::DATEINTERVAL => DateIntervalType::class, Types::DATETIME_MUTABLE => DateTimeType::class, Types::DATETIME_IMMUTABLE => DateTimeImmutableType::class, Types::DATETIMETZ_MUTABLE => DateTimeTzType::class, Types::DATETIMETZ_IMMUTABLE => DateTimeTzImmutableType::class, Types::DECIMAL => DecimalType::class, Types::FLOAT => FloatType::class, Types::GUID => GuidType::class, Types::INTEGER => IntegerType::class, Types::JSON => JsonType::class, Types::OBJECT => ObjectType::class, Types::SIMPLE_ARRAY => SimpleArrayType::class, Types::SMALLINT => SmallIntType::class, Types::STRING => StringType::class, Types::TEXT => TextType::class, Types::TIME_MUTABLE => TimeType::class, Types::TIME_IMMUTABLE => TimeImmutableType::class, ]; private static ?TypeRegistry $typeRegistry = null; /** @internal Do not instantiate directly - use {@see Type::addType()} method instead. */ final public function __construct() { } /** * Converts a value from its PHP representation to its database representation * of this type. * * @param mixed $value The value to convert. * @param AbstractPlatform $platform The currently used database platform. * * @return mixed The database representation of the value. * * @throws ConversionException */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return $value; } /** * Converts a value from its database representation to its PHP representation * of this type. * * @param mixed $value The value to convert. * @param AbstractPlatform $platform The currently used database platform. * * @return mixed The PHP representation of the value. * * @throws ConversionException */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value; } /** * Gets the SQL declaration snippet for a column of this type. * * @param mixed[] $column The column definition * @param AbstractPlatform $platform The currently used database platform. * * @return string */ abstract public function getSQLDeclaration(array $column, AbstractPlatform $platform); /** * Gets the name of this type. * * @deprecated this method will be removed in Doctrine DBAL 4.0, * use {@see TypeRegistry::lookupName()} instead. * * @return string */ abstract public function getName(); final public static function getTypeRegistry(): TypeRegistry { return self::$typeRegistry ??= self::createTypeRegistry(); } private static function createTypeRegistry(): TypeRegistry { $instances = []; foreach (self::BUILTIN_TYPES_MAP as $name => $class) { $instances[$name] = new $class(); } return new TypeRegistry($instances); } /** * Factory method to create type instances. * Type instances are implemented as flyweights. * * @param string $name The name of the type (as returned by getName()). * * @return Type * * @throws Exception */ public static function getType($name) { return self::getTypeRegistry()->get($name); } /** * Finds a name for the given type. * * @throws Exception */ public static function lookupName(self $type): string { return self::getTypeRegistry()->lookupName($type); } /** * Adds a custom type to the type map. * * @param string $name The name of the type. This should correspond to what getName() returns. * @param class-string<Type> $className The class name of the custom type. * * @return void * * @throws Exception */ public static function addType($name, $className) { self::getTypeRegistry()->register($name, new $className()); } /** * Checks if exists support for a type. * * @param string $name The name of the type. * * @return bool TRUE if type is supported; FALSE otherwise. */ public static function hasType($name) { return self::getTypeRegistry()->has($name); } /** * Overrides an already defined type to use a different implementation. * * @param string $name * @param class-string<Type> $className * * @return void * * @throws Exception */ public static function overrideType($name, $className) { self::getTypeRegistry()->override($name, new $className()); } /** * Gets the (preferred) binding type for values of this type that * can be used when binding parameters to prepared statements. * * This method should return one of the {@see ParameterType} constants. * * @return int */ public function getBindingType() { return ParameterType::STRING; } /** * Gets the types array map which holds all registered types and the corresponding * type class * * @return array<string, string> */ public static function getTypesMap() { return array_map( static function (Type $type): string { return get_class($type); }, self::getTypeRegistry()->getMap(), ); } /** * Does working with this column require SQL conversion functions? * * This is a metadata function that is required for example in the ORM. * Usage of {@see convertToDatabaseValueSQL} and * {@see convertToPHPValueSQL} works for any type and mostly * does nothing. This method can additionally be used for optimization purposes. * * @deprecated Consumers should call {@see convertToDatabaseValueSQL} and {@see convertToPHPValueSQL} * regardless of the type. * * @return bool */ public function canRequireSQLConversion() { return false; } /** * Modifies the SQL expression (identifier, parameter) to convert to a database value. * * @param string $sqlExpr * * @return string */ public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform) { return $sqlExpr; } /** * Modifies the SQL expression (identifier, parameter) to convert to a PHP value. * * @param string $sqlExpr * @param AbstractPlatform $platform * * @return string */ public function convertToPHPValueSQL($sqlExpr, $platform) { return $sqlExpr; } /** * Gets an array of database types that map to this Doctrine type. * * @return string[] */ public function getMappedDatabaseTypes(AbstractPlatform $platform) { return []; } /** * If this Doctrine Type maps to an already mapped database type, * reverse schema engineering can't tell them apart. You need to mark * one of those types as commented, which will have Doctrine use an SQL * comment to typehint the actual Doctrine Type. * * @deprecated * * @return bool */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return false; } } dbal/src/Types/VarDateTimeType.php 0000644 00000002063 15021222234 0013053 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; use Exception; /** * Variable DateTime Type using DateTime::__construct() instead of DateTime::createFromFormat(). * * This type has performance implications as it runs twice as long as the regular * {@see DateTimeType}, however in certain PostgreSQL configurations with * TIMESTAMP(n) columns where n > 0 it is necessary to use this type. */ class VarDateTimeType extends DateTimeType { /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeInterface) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTime) { return $value; } try { $dateTime = new DateTime($value); } catch (Exception $e) { throw ConversionException::conversionFailed($value, $this->getName(), $e); } return $dateTime; } } dbal/src/Types/TextType.php 0000644 00000001335 15021222234 0011633 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use function is_resource; use function stream_get_contents; /** * Type that maps an SQL CLOB to a PHP string. */ class TextType extends Type { /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { return is_resource($value) ? stream_get_contents($value) : $value; } /** * {@inheritDoc} */ public function getName() { return Types::TEXT; } } dbal/src/Types/TimeImmutableType.php 0000644 00000003762 15021222234 0013453 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; /** * Immutable type of {@see TimeType}. */ class TimeImmutableType extends TimeType { /** * {@inheritDoc} */ public function getName() { return Types::TIME_IMMUTABLE; } /** * {@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) { return $value->format($platform->getTimeFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeImmutable) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat('!' . $platform->getTimeFormatString(), $value); if ($dateTime !== false) { return $dateTime; } throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getTimeFormatString(), ); } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } dbal/src/Types/DateTimeImmutableType.php 0000644 00000004252 15021222234 0014244 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use Exception; /** * Immutable type of {@see DateTimeType}. */ class DateTimeImmutableType extends DateTimeType { /** * {@inheritDoc} */ public function getName() { return Types::DATETIME_IMMUTABLE; } /** * {@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) { return $value->format($platform->getDateTimeFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeImmutable) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat($platform->getDateTimeFormatString(), $value); if ($dateTime !== false) { return $dateTime; } try { return new DateTimeImmutable($value); } catch (Exception $e) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateTimeFormatString(), $e, ); } } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } dbal/src/Types/DateIntervalType.php 0000644 00000004653 15021222234 0013277 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateInterval; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use Throwable; use function substr; /** * Type that maps interval string to a PHP DateInterval Object. */ class DateIntervalType extends Type { public const FORMAT = '%RP%YY%MM%DDT%HH%IM%SS'; /** * {@inheritDoc} */ public function getName() { return Types::DATEINTERVAL; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { $column['length'] = 255; return $platform->getStringTypeDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : string) * * @template T */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } if ($value instanceof DateInterval) { return $value->format(self::FORMAT); } throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', DateInterval::class]); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateInterval) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateInterval) { return $value; } $negative = false; if (isset($value[0]) && ($value[0] === '+' || $value[0] === '-')) { $negative = $value[0] === '-'; $value = substr($value, 1); } try { $interval = new DateInterval($value); if ($negative) { $interval->invert = 1; } return $interval; } catch (Throwable $exception) { throw ConversionException::conversionFailedFormat($value, $this->getName(), self::FORMAT, $exception); } } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } dbal/src/Types/TimeType.php 0000644 00000005171 15021222234 0011607 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeImmutable; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function get_class; /** * Type that maps an SQL TIME to a PHP DateTime object. */ class TimeType extends Type { /** * {@inheritDoc} */ public function getName() { return Types::TIME_MUTABLE; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getTimeTypeDeclarationSQL($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), TimeImmutableType::class, __FUNCTION__, ); } if ($value instanceof DateTimeInterface) { return $value->format($platform->getTimeFormatString()); } 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), TimeImmutableType::class, __FUNCTION__, ); } if ($value === null || $value instanceof DateTimeInterface) { return $value; } $dateTime = DateTime::createFromFormat('!' . $platform->getTimeFormatString(), $value); if ($dateTime !== false) { return $dateTime; } throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getTimeFormatString(), ); } } dbal/src/Types/FloatType.php 0000644 00000001276 15021222234 0011760 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; class FloatType extends Type { /** * {@inheritDoc} */ public function getName() { return Types::FLOAT; } /** * {@inheritDoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getFloatDeclarationSQL($column); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : float) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (float) $value; } } dbal/src/Types/VarDateTimeImmutableType.php 0000644 00000003644 15021222234 0014721 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use Exception; /** * Immutable type of {@see VarDateTimeType}. */ class VarDateTimeImmutableType extends VarDateTimeType { /** * {@inheritDoc} */ public function getName() { return Types::DATETIME_IMMUTABLE; } /** * {@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) { return $value->format($platform->getDateTimeFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class], ); } /** * {@inheritDoc} * * @param T $value * * @return (T is null ? null : DateTimeImmutable) * * @template T */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } try { $dateTime = new DateTimeImmutable($value); } catch (Exception $e) { throw ConversionException::conversionFailed($value, $this->getName(), $e); } return $dateTime; } /** * {@inheritDoc} * * @deprecated */ public function requiresSQLCommentHint(AbstractPlatform $platform) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } } dbal/src/Types/DateTimeTzType.php 0000644 00000007342 15021222234 0012725 0 ustar 00 <?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(), ); } } dbal/src/Result.php 0000644 00000021122 15021222234 0010213 0 ustar 00 <?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.'); } } dbal/src/Cache/CacheException.php 0000644 00000000715 15021222234 0012627 0 ustar 00 <?php namespace Doctrine\DBAL\Cache; use Doctrine\DBAL\Exception; /** @psalm-immutable */ class CacheException extends Exception { /** @return CacheException */ public static function noCacheKey() { return new self('No cache key was set.'); } /** @return CacheException */ public static function noResultDriverConfigured() { return new self('Trying to cache a query but no result driver is configured.'); } } dbal/src/Cache/QueryCacheProfile.php 0000644 00000012161 15021222234 0013315 0 ustar 00 <?php namespace Doctrine\DBAL\Cache; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use Psr\Cache\CacheItemPoolInterface; use TypeError; use function get_class; use function hash; use function serialize; use function sha1; use function sprintf; /** * Query Cache Profile handles the data relevant for query caching. * * It is a value object, setter methods return NEW instances. */ class QueryCacheProfile { private ?CacheItemPoolInterface $resultCache = null; /** @var int */ private $lifetime; /** @var string|null */ private $cacheKey; /** * @param int $lifetime * @param string|null $cacheKey * @param CacheItemPoolInterface|Cache|null $resultCache */ public function __construct($lifetime = 0, $cacheKey = null, ?object $resultCache = null) { $this->lifetime = $lifetime; $this->cacheKey = $cacheKey; if ($resultCache instanceof CacheItemPoolInterface) { $this->resultCache = $resultCache; } elseif ($resultCache instanceof Cache) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4620', 'Passing an instance of %s to %s as $resultCache is deprecated. Pass an instance of %s instead.', Cache::class, __METHOD__, CacheItemPoolInterface::class, ); $this->resultCache = CacheAdapter::wrap($resultCache); } elseif ($resultCache !== null) { throw new TypeError(sprintf( '$resultCache: Expected either null or an instance of %s or %s, got %s.', CacheItemPoolInterface::class, Cache::class, get_class($resultCache), )); } } public function getResultCache(): ?CacheItemPoolInterface { return $this->resultCache; } /** * @deprecated Use {@see getResultCache()} instead. * * @return Cache|null */ public function getResultCacheDriver() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4620', '%s is deprecated, call getResultCache() instead.', __METHOD__, ); return $this->resultCache !== null ? DoctrineProvider::wrap($this->resultCache) : null; } /** @return int */ public function getLifetime() { return $this->lifetime; } /** * @return string * * @throws CacheException */ public function getCacheKey() { if ($this->cacheKey === null) { throw CacheException::noCacheKey(); } return $this->cacheKey; } /** * Generates the real cache key from query, params, types and connection parameters. * * @param string $sql * @param list<mixed>|array<string, mixed> $params * @param array<int, Type|int|string|null>|array<string, Type|int|string|null> $types * @param array<string, mixed> $connectionParams * * @return array{string, string} */ public function generateCacheKeys($sql, $params, $types, array $connectionParams = []) { if (isset($connectionParams['password'])) { unset($connectionParams['password']); } $realCacheKey = 'query=' . $sql . '¶ms=' . serialize($params) . '&types=' . serialize($types) . '&connectionParams=' . hash('sha256', serialize($connectionParams)); // should the key be automatically generated using the inputs or is the cache key set? $cacheKey = $this->cacheKey ?? sha1($realCacheKey); return [$cacheKey, $realCacheKey]; } public function setResultCache(CacheItemPoolInterface $cache): QueryCacheProfile { return new QueryCacheProfile($this->lifetime, $this->cacheKey, $cache); } /** * @deprecated Use {@see setResultCache()} instead. * * @return QueryCacheProfile */ public function setResultCacheDriver(Cache $cache) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4620', '%s is deprecated, call setResultCache() instead.', __METHOD__, ); return new QueryCacheProfile($this->lifetime, $this->cacheKey, CacheAdapter::wrap($cache)); } /** * @param string|null $cacheKey * * @return QueryCacheProfile */ public function setCacheKey($cacheKey) { return new QueryCacheProfile($this->lifetime, $cacheKey, $this->resultCache); } /** * @param int $lifetime * * @return QueryCacheProfile */ public function setLifetime($lifetime) { return new QueryCacheProfile($lifetime, $this->cacheKey, $this->resultCache); } } dbal/src/Cache/ArrayResult.php 0000644 00000004133 15021222234 0012220 0 ustar 00 <?php namespace Doctrine\DBAL\Cache; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result; use function array_values; use function count; use function reset; /** @internal The class is internal to the caching layer implementation. */ final class ArrayResult implements Result { /** @var list<array<string, mixed>> */ private array $data; private int $columnCount = 0; private int $num = 0; /** @param list<array<string, mixed>> $data */ public function __construct(array $data) { $this->data = $data; if (count($data) === 0) { return; } $this->columnCount = count($data[0]); } /** * {@inheritDoc} */ public function fetchNumeric() { $row = $this->fetch(); if ($row === false) { return false; } return array_values($row); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->fetch(); } /** * {@inheritDoc} */ public function fetchOne() { $row = $this->fetch(); if ($row === false) { return false; } return reset($row); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } public function rowCount(): int { return count($this->data); } public function columnCount(): int { return $this->columnCount; } public function free(): void { $this->data = []; } /** @return array<string, mixed>|false */ private function fetch() { if (! isset($this->data[$this->num])) { return false; } return $this->data[$this->num++]; } } dbal/src/ConnectionException.php 0000644 00000001625 15021222234 0012721 0 ustar 00 <?php namespace Doctrine\DBAL; /** @psalm-immutable */ class ConnectionException extends Exception { /** @return ConnectionException */ public static function commitFailedRollbackOnly() { return new self('Transaction commit failed because the transaction has been marked for rollback only.'); } /** @return ConnectionException */ public static function noActiveTransaction() { return new self('There is no active transaction.'); } /** @return ConnectionException */ public static function savepointsNotSupported() { return new self('Savepoints are not supported by this driver.'); } /** @return ConnectionException */ public static function mayNotAlterNestedTransactionWithSavepointsInTransaction() { return new self('May not alter the nested transaction with savepoints behavior while a transaction is open.'); } } dbal/src/Configuration.php 0000644 00000016256 15021222234 0011560 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Logging\SQLLogger; use Doctrine\DBAL\Schema\SchemaManagerFactory; use Doctrine\Deprecations\Deprecation; use Psr\Cache\CacheItemPoolInterface; use function func_num_args; /** * Configuration container for the Doctrine DBAL. */ class Configuration { /** @var Middleware[] */ private array $middlewares = []; /** * The SQL logger in use. If null, SQL logging is disabled. * * @var SQLLogger|null */ protected $sqlLogger; /** * The cache driver implementation that is used for query result caching. */ private ?CacheItemPoolInterface $resultCache = null; /** * The cache driver implementation that is used for query result caching. * * @deprecated Use {@see $resultCache} instead. * * @var Cache|null */ protected $resultCacheImpl; /** * The callable to use to filter schema assets. * * @var callable|null */ protected $schemaAssetsFilter; /** * The default auto-commit mode for connections. * * @var bool */ protected $autoCommit = true; /** * Whether type comments should be disabled to provide the same DB schema than * will be obtained with DBAL 4.x. This is useful when relying only on the * platform-aware schema comparison (which does not need those type comments) * rather than the deprecated legacy tooling. */ private bool $disableTypeComments = false; private ?SchemaManagerFactory $schemaManagerFactory = null; public function __construct() { $this->schemaAssetsFilter = static function (): bool { return true; }; } /** * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. * * @deprecated Use {@see setMiddlewares()} and {@see \Doctrine\DBAL\Logging\Middleware} instead. */ public function setSQLLogger(?SQLLogger $logger = null): void { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4967', '%s is deprecated, use setMiddlewares() and Logging\\Middleware instead.', __METHOD__, ); $this->sqlLogger = $logger; } /** * Gets the SQL logger that is used. * * @deprecated */ public function getSQLLogger(): ?SQLLogger { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4967', '%s is deprecated.', __METHOD__, ); return $this->sqlLogger; } /** * Gets the cache driver implementation that is used for query result caching. */ public function getResultCache(): ?CacheItemPoolInterface { return $this->resultCache; } /** * Gets the cache driver implementation that is used for query result caching. * * @deprecated Use {@see getResultCache()} instead. */ public function getResultCacheImpl(): ?Cache { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4620', '%s is deprecated, call getResultCache() instead.', __METHOD__, ); return $this->resultCacheImpl; } /** * Sets the cache driver implementation that is used for query result caching. */ public function setResultCache(CacheItemPoolInterface $cache): void { $this->resultCacheImpl = DoctrineProvider::wrap($cache); $this->resultCache = $cache; } /** * Sets the cache driver implementation that is used for query result caching. * * @deprecated Use {@see setResultCache()} instead. */ public function setResultCacheImpl(Cache $cacheImpl): void { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4620', '%s is deprecated, call setResultCache() instead.', __METHOD__, ); $this->resultCacheImpl = $cacheImpl; $this->resultCache = CacheAdapter::wrap($cacheImpl); } /** * Sets the callable to use to filter schema assets. */ public function setSchemaAssetsFilter(?callable $callable = null): void { if (func_num_args() < 1) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5483', 'Not passing an argument to %s is deprecated.', __METHOD__, ); } elseif ($callable === null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5483', 'Using NULL as a schema asset filter is deprecated.' . ' Use a callable that always returns true instead.', ); } $this->schemaAssetsFilter = $callable; } /** * Returns the callable to use to filter schema assets. */ public function getSchemaAssetsFilter(): ?callable { return $this->schemaAssetsFilter; } /** * Sets the default auto-commit mode for connections. * * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either * the method commit or the method rollback. By default, new connections are in auto-commit mode. * * @see getAutoCommit * * @param bool $autoCommit True to enable auto-commit mode; false to disable it */ public function setAutoCommit(bool $autoCommit): void { $this->autoCommit = $autoCommit; } /** * Returns the default auto-commit mode for connections. * * @see setAutoCommit * * @return bool True if auto-commit mode is enabled by default for connections, false otherwise. */ public function getAutoCommit(): bool { return $this->autoCommit; } /** * @param Middleware[] $middlewares * * @return $this */ public function setMiddlewares(array $middlewares): self { $this->middlewares = $middlewares; return $this; } /** @return Middleware[] */ public function getMiddlewares(): array { return $this->middlewares; } public function getSchemaManagerFactory(): ?SchemaManagerFactory { return $this->schemaManagerFactory; } /** @return $this */ public function setSchemaManagerFactory(SchemaManagerFactory $schemaManagerFactory): self { $this->schemaManagerFactory = $schemaManagerFactory; return $this; } public function getDisableTypeComments(): bool { return $this->disableTypeComments; } /** @return $this */ public function setDisableTypeComments(bool $disableTypeComments): self { $this->disableTypeComments = $disableTypeComments; return $this; } } dbal/src/Platforms/DB2Platform.php 0000644 00000075274 15021222234 0013001 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\DB2SchemaManager; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use function array_merge; use function count; use function current; use function explode; use function func_get_arg; use function func_num_args; use function implode; use function sprintf; use function strpos; /** * Provides the behavior, features and SQL dialect of the IBM DB2 database platform of the oldest supported version. */ class DB2Platform extends AbstractPlatform { /** @see https://www.ibm.com/docs/en/db2/11.5?topic=views-syscatcolumns */ private const SYSCAT_COLUMNS_GENERATED_DEFAULT = 'D'; /** @see https://www.ibm.com/docs/en/db2/11.5?topic=views-syscatindexes */ private const SYSCAT_INDEXES_UNIQUERULE_PERMITS_DUPLICATES = 'D'; private const SYSCAT_INDEXES_UNIQUERULE_IMPLEMENTS_PRIMARY_KEY = 'P'; /** @see https://www.ibm.com/docs/en/db2/11.5?topic=views-syscattabconst */ private const SYSCAT_TABCONST_TYPE_PRIMARY_KEY = 'P'; /** @see https://www.ibm.com/docs/en/db2/11.5?topic=views-syscatreferences */ private const SYSCAT_REFERENCES_UPDATERULE_RESTRICT = 'R'; private const SYSCAT_REFERENCES_DELETERULE_CASCADE = 'C'; private const SYSCAT_REFERENCES_DELETERULE_SET_NULL = 'N'; private const SYSCAT_REFERENCES_DELETERULE_RESTRICT = 'R'; /** @see https://www.ibm.com/docs/en/db2-for-zos/11?topic=tables-systables */ private const SYSIBM_SYSTABLES_TYPE_TABLE = 'T'; /** * {@inheritDoc} * * @deprecated */ public function getCharMaxLength(): int { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', '%s() is deprecated.', __METHOD__, ); return 254; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', '%s() is deprecated.', __METHOD__, ); return 32704; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryDefaultLength() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length is deprecated, specify the length explicitly.', ); return 1; } /** * {@inheritDoc} */ public function getVarcharTypeDeclarationSQL(array $column) { // for IBM DB2, the CHAR max length is less than VARCHAR default length if (! isset($column['length']) && ! empty($column['fixed'])) { $column['length'] = $this->getCharMaxLength(); } return parent::getVarcharTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { // todo blob(n) with $column['length']; return 'BLOB(1M)'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'bigint' => Types::BIGINT, 'binary' => Types::BINARY, 'blob' => Types::BLOB, 'character' => Types::STRING, 'clob' => Types::TEXT, 'date' => Types::DATE_MUTABLE, 'decimal' => Types::DECIMAL, 'double' => Types::FLOAT, 'integer' => Types::INTEGER, 'real' => Types::FLOAT, 'smallint' => Types::SMALLINT, 'time' => Types::TIME_MUTABLE, 'timestamp' => Types::DATETIME_MUTABLE, 'varbinary' => Types::BINARY, 'varchar' => Types::STRING, ]; } /** * {@inheritDoc} */ public function isCommentedDoctrineType(Type $doctrineType) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5058', '%s() is deprecated and will be removed in Doctrine DBAL 4.0. Use Type::requiresSQLCommentHint() instead.', __METHOD__, ); if ($doctrineType->getName() === Types::BOOLEAN) { // We require a commented boolean type in order to distinguish between boolean and smallint // as both (have to) map to the same native type. return true; } return parent::isCommentedDoctrineType($doctrineType); } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default string column length on IBM DB2 is deprecated' . ', specify the length explicitly.', ); } return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(254)') : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length on IBM DB2 is deprecated' . ', specify the length explicitly.', ); } return $this->getVarcharTypeDeclarationSQLSnippet($length, $fixed) . ' FOR BIT DATA'; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { // todo clob(n) with $column['length']; return 'CLOB(1M)'; } /** * {@inheritDoc} */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4749', '%s() is deprecated. Identify platforms by their class.', __METHOD__, ); return 'db2'; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'SMALLINT'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { $autoinc = ''; if (! empty($column['autoincrement'])) { $autoinc = ' GENERATED BY DEFAULT AS IDENTITY'; } return $autoinc; } /** * {@inheritDoc} */ public function getBitAndComparisonExpression($value1, $value2) { return 'BITAND(' . $value1 . ', ' . $value2 . ')'; } /** * {@inheritDoc} */ public function getBitOrComparisonExpression($value1, $value2) { return 'BITOR(' . $value1 . ', ' . $value2 . ')'; } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { switch ($unit) { case DateIntervalUnit::WEEK: $interval = $this->multiplyInterval((string) $interval, 7); $unit = DateIntervalUnit::DAY; break; case DateIntervalUnit::QUARTER: $interval = $this->multiplyInterval((string) $interval, 3); $unit = DateIntervalUnit::MONTH; break; } return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { if (isset($column['version']) && $column['version'] === true) { return 'TIMESTAMP(0) WITH DEFAULT'; } return 'TIMESTAMP(0)'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME'; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this) . ' IMMEDIATE'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * This code fragment is originally from the Zend_Db_Adapter_Db2 class, but has been edited. * * @param string $table * @param string $database * * @return string */ public function getListTableColumnsSQL($table, $database = null) { $table = $this->quoteStringLiteral($table); // We do the funky subquery and join syscat.columns.default this crazy way because // as of db2 v10, the column is CLOB(64k) and the distinct operator won't allow a CLOB, // it wants shorter stuff like a varchar. return " SELECT cols.default, subq.* FROM ( SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno, c.typename, c.codepage, c.nulls, c.length, c.scale, c.identity, tc.type AS tabconsttype, c.remarks AS comment, k.colseq, CASE WHEN c.generated = '" . self::SYSCAT_COLUMNS_GENERATED_DEFAULT . "' THEN 1 ELSE 0 END AS autoincrement FROM syscat.columns c LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc ON (k.tabschema = tc.tabschema AND k.tabname = tc.tabname AND tc.type = '" . self::SYSCAT_TABCONST_TYPE_PRIMARY_KEY . "')) ON (c.tabschema = k.tabschema AND c.tabname = k.tabname AND c.colname = k.colname) WHERE UPPER(c.tabname) = UPPER(" . $table . ') ORDER BY c.colno ) subq JOIN syscat.columns cols ON subq.tabschema = cols.tabschema AND subq.tabname = cols.tabname AND subq.colno = cols.colno ORDER BY subq.colno '; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = '" . self::SYSIBM_SYSTABLES_TYPE_TABLE . "'" . ' AND CREATOR = CURRENT_USER'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return 'SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) { $table = $this->quoteStringLiteral($table); return "SELECT idx.INDNAME AS key_name, idxcol.COLNAME AS column_name, CASE WHEN idx.UNIQUERULE = '" . self::SYSCAT_INDEXES_UNIQUERULE_IMPLEMENTS_PRIMARY_KEY . "' THEN 1 ELSE 0 END AS primary, CASE WHEN idx.UNIQUERULE = '" . self::SYSCAT_INDEXES_UNIQUERULE_PERMITS_DUPLICATES . "' THEN 1 ELSE 0 END AS non_unique FROM SYSCAT.INDEXES AS idx JOIN SYSCAT.INDEXCOLUSE AS idxcol ON idx.INDSCHEMA = idxcol.INDSCHEMA AND idx.INDNAME = idxcol.INDNAME WHERE idx.TABNAME = UPPER(" . $table . ') ORDER BY idxcol.COLSEQ ASC'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableForeignKeysSQL($table) { $table = $this->quoteStringLiteral($table); return "SELECT fkcol.COLNAME AS local_column, fk.REFTABNAME AS foreign_table, pkcol.COLNAME AS foreign_column, fk.CONSTNAME AS index_name, CASE WHEN fk.UPDATERULE = '" . self::SYSCAT_REFERENCES_UPDATERULE_RESTRICT . "' THEN 'RESTRICT' ELSE NULL END AS on_update, CASE WHEN fk.DELETERULE = '" . self::SYSCAT_REFERENCES_DELETERULE_CASCADE . "' THEN 'CASCADE' WHEN fk.DELETERULE = '" . self::SYSCAT_REFERENCES_DELETERULE_SET_NULL . "' THEN 'SET NULL' WHEN fk.DELETERULE = '" . self::SYSCAT_REFERENCES_DELETERULE_RESTRICT . "' THEN 'RESTRICT' ELSE NULL END AS on_delete FROM SYSCAT.REFERENCES AS fk JOIN SYSCAT.KEYCOLUSE AS fkcol ON fk.CONSTNAME = fkcol.CONSTNAME AND fk.TABSCHEMA = fkcol.TABSCHEMA AND fk.TABNAME = fkcol.TABNAME JOIN SYSCAT.KEYCOLUSE AS pkcol ON fk.REFKEYNAME = pkcol.CONSTNAME AND fk.REFTABSCHEMA = pkcol.TABSCHEMA AND fk.REFTABNAME = pkcol.TABNAME WHERE fk.TABNAME = UPPER(" . $table . ') ORDER BY fkcol.COLSEQ ASC'; } /** * {@inheritDoc} * * @deprecated */ public function supportsCreateDropDatabase() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s() is deprecated.', __METHOD__, ); return false; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsCommentOnStatement() { return true; } /** * {@inheritDoc} */ public function getCurrentDateSQL() { return 'CURRENT DATE'; } /** * {@inheritDoc} */ public function getCurrentTimeSQL() { return 'CURRENT TIME'; } /** * {@inheritDoc} */ public function getCurrentTimestampSQL() { return 'CURRENT TIMESTAMP'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getIndexDeclarationSQL($name, Index $index) { // Index declaration in statements like CREATE TABLE is not supported. throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $indexes = []; if (isset($options['indexes'])) { $indexes = $options['indexes']; } $options['indexes'] = []; $sqls = parent::_getCreateTableSQL($name, $columns, $options); foreach ($indexes as $definition) { $sqls[] = $this->getCreateIndexSQL($definition, $name); } return $sqls; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = []; $columnSql = []; $commentsSQL = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); $queryParts = []; foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columnDef = $column->toArray(); $queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); // Adding non-nullable columns to a table requires a default value to be specified. if ( ! empty($columnDef['notnull']) && ! isset($columnDef['default']) && empty($columnDef['autoincrement']) ) { $queryPart .= ' WITH DEFAULT'; } $queryParts[] = $queryPart; $comment = $this->getColumnComment($column); if ($comment === null || $comment === '') { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $column->getQuotedName($this), $comment, ); } foreach ($diff->getDroppedColumns() as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); } foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } if ($columnDiff->hasCommentChanged()) { $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $columnDiff->getNewColumn()->getQuotedName($this), $this->getColumnComment($columnDiff->getNewColumn()), ); } $this->gatherAlterColumnSQL( $tableNameSQL, $columnDiff, $sql, $queryParts, ); } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $queryParts[] = 'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); } $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { if (count($queryParts) > 0) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . implode(' ', $queryParts); } // Some table alteration operations require a table reorganization. if (count($diff->getDroppedColumns()) > 0 || count($diff->getModifiedColumns()) > 0) { $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $tableNameSQL . "')"; } $sql = array_merge($sql, $commentsSQL); $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of "rename table" SQL using %s() is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $sql[] = sprintf( 'RENAME TABLE %s TO %s', $tableNameSQL, $newName->getQuotedName($this), ); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff), ); } return array_merge($sql, $tableSql, $columnSql); } /** * {@inheritDoc} */ public function getRenameTableSQL(string $oldName, string $newName): array { return [ sprintf('RENAME TABLE %s TO %s', $oldName, $newName), ]; } /** * Gathers the table alteration SQL for a given column diff. * * @param string $table The table to gather the SQL for. * @param ColumnDiff $columnDiff The column diff to evaluate. * @param string[] $sql The sequence of table alteration statements to fill. * @param mixed[] $queryParts The sequence of column alteration clauses to fill. */ private function gatherAlterColumnSQL( string $table, ColumnDiff $columnDiff, array &$sql, array &$queryParts ): void { $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff); if (empty($alterColumnClauses)) { return; } // If we have a single column alteration, we can append the clause to the main query. if (count($alterColumnClauses) === 1) { $queryParts[] = current($alterColumnClauses); return; } // We have multiple alterations for the same column, // so we need to trigger a complete ALTER TABLE statement // for each ALTER COLUMN clause. foreach ($alterColumnClauses as $alterColumnClause) { $sql[] = 'ALTER TABLE ' . $table . ' ' . $alterColumnClause; } } /** * Returns the ALTER COLUMN SQL clauses for altering a column described by the given column diff. * * @return string[] */ private function getAlterColumnClausesSQL(ColumnDiff $columnDiff): array { $newColumn = $columnDiff->getNewColumn()->toArray(); $alterClause = 'ALTER COLUMN ' . $columnDiff->getNewColumn()->getQuotedName($this); if ($newColumn['columnDefinition'] !== null) { return [$alterClause . ' ' . $newColumn['columnDefinition']]; } $clauses = []; if ( $columnDiff->hasTypeChanged() || $columnDiff->hasLengthChanged() || $columnDiff->hasPrecisionChanged() || $columnDiff->hasScaleChanged() || $columnDiff->hasFixedChanged() ) { $clauses[] = $alterClause . ' SET DATA TYPE ' . $newColumn['type']->getSQLDeclaration($newColumn, $this); } if ($columnDiff->hasNotNullChanged()) { $clauses[] = $newColumn['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL'; } if ($columnDiff->hasDefaultChanged()) { if (isset($newColumn['default'])) { $defaultClause = $this->getDefaultValueDeclarationSQL($newColumn); if ($defaultClause !== '') { $clauses[] = $alterClause . ' SET' . $defaultClause; } } else { $clauses[] = $alterClause . ' DROP DEFAULT'; } } return $clauses; } /** * {@inheritDoc} */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { $sql = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); foreach ($diff->getDroppedIndexes() as $droppedIndex) { foreach ($diff->getAddedIndexes() as $addedIndex) { if ($droppedIndex->getColumns() !== $addedIndex->getColumns()) { continue; } if ($droppedIndex->isPrimary()) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP PRIMARY KEY'; } elseif ($droppedIndex->isUnique()) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP UNIQUE ' . $droppedIndex->getQuotedName($this); } else { $sql[] = $this->getDropIndexSQL($droppedIndex, $tableNameSQL); } $sql[] = $this->getCreateIndexSQL($addedIndex, $tableNameSQL); $diff->unsetAddedIndex($addedIndex); $diff->unsetDroppedIndex($droppedIndex); break; } } return array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff)); } /** * {@inheritDoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { if (strpos($tableName, '.') !== false) { [$schema] = explode('.', $tableName); $oldIndexName = $schema . '.' . $oldIndexName; } return ['RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)]; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getDefaultValueDeclarationSQL($column) { if (! empty($column['autoincrement'])) { return ''; } if (! empty($column['version'])) { if ((string) $column['type'] !== 'DateTime') { $column['default'] = '1'; } } return parent::getDefaultValueDeclarationSQL($column); } /** * {@inheritDoc} */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; } /** * {@inheritDoc} */ public function getCreateTemporaryTableSnippetSQL() { return 'DECLARE GLOBAL TEMPORARY TABLE'; } /** * {@inheritDoc} */ public function getTemporaryTableName($tableName) { return 'SESSION.' . $tableName; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { $where = []; if ($offset > 0) { $where[] = sprintf('db22.DC_ROWNUM >= %d', $offset + 1); } if ($limit !== null) { $where[] = sprintf('db22.DC_ROWNUM <= %d', $offset + $limit); } if (empty($where)) { return $query; } // Todo OVER() needs ORDER BY data! return sprintf( 'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s', $query, implode(' AND ', $where), ); } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'LOCATE(' . $substr . ', ' . $str . ')'; } return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')'; } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length === null) { return 'SUBSTR(' . $string . ', ' . $start . ')'; } return 'SUBSTR(' . $string . ', ' . $start . ', ' . $length . ')'; } /** * {@inheritDoc} */ public function getLengthExpression($column) { return 'LENGTH(' . $column . ', CODEUNITS32)'; } public function getCurrentDatabaseExpression(): string { return 'CURRENT_USER'; } /** * {@inheritDoc} */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} * * @deprecated */ public function prefersIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/1519', '%s() is deprecated.', __METHOD__, ); return true; } public function createSelectSQLBuilder(): SelectSQLBuilder { return new DefaultSelectSQLBuilder($this, 'WITH RR USE AND KEEP UPDATE LOCKS', null); } /** * {@inheritDoc} * * @deprecated This API is not portable. */ public function getForUpdateSQL() { return ' WITH RR USE AND KEEP UPDATE LOCKS'; } /** * {@inheritDoc} */ public function getDummySelectSQL() { $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; return sprintf('SELECT %s FROM sysibm.sysdummy1', $expression); } /** * {@inheritDoc} * * DB2 supports savepoints, but they work semantically different than on other vendor platforms. * * TODO: We have to investigate how to get DB2 up and running with savepoints. */ public function supportsSavepoints() { return false; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', '%s() is deprecated,' . ' use %s::createReservedKeywordsList() instead.', __METHOD__, static::class, ); return Keywords\DB2Keywords::class; } /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ public function getListTableCommentsSQL(string $table): string { return sprintf( <<<'SQL' SELECT REMARKS FROM SYSIBM.SYSTABLES WHERE NAME = UPPER( %s ) SQL , $this->quoteStringLiteral($table), ); } public function createSchemaManager(Connection $connection): DB2SchemaManager { return new DB2SchemaManager($connection, $this); } } dbal/src/Platforms/AbstractMySQLPlatform.php 0000644 00000133046 15021222234 0015053 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\AbstractAsset; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\MySQLSchemaManager; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types\BlobType; use Doctrine\DBAL\Types\TextType; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use function array_diff_key; use function array_merge; use function array_unique; use function array_values; use function count; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function in_array; use function is_numeric; use function is_string; use function sprintf; use function str_replace; use function strcasecmp; use function strtolower; use function strtoupper; use function trim; /** * Provides the base implementation for the lowest versions of supported MySQL-like database platforms. */ abstract class AbstractMySQLPlatform extends AbstractPlatform { public const LENGTH_LIMIT_TINYTEXT = 255; public const LENGTH_LIMIT_TEXT = 65535; public const LENGTH_LIMIT_MEDIUMTEXT = 16777215; public const LENGTH_LIMIT_TINYBLOB = 255; public const LENGTH_LIMIT_BLOB = 65535; public const LENGTH_LIMIT_MEDIUMBLOB = 16777215; /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit !== null) { $query .= sprintf(' LIMIT %d', $limit); if ($offset > 0) { $query .= sprintf(' OFFSET %d', $offset); } } elseif ($offset > 0) { // 2^64-1 is the maximum of unsigned BIGINT, the biggest limit possible $query .= sprintf(' LIMIT 18446744073709551615 OFFSET %d', $offset); } return $query; } /** * {@inheritDoc} * * @deprecated Use {@see quoteIdentifier()} to quote identifiers instead. */ public function getIdentifierQuoteCharacter() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5388', 'AbstractMySQLPlatform::getIdentifierQuoteCharacter() is deprecated. Use quoteIdentifier() instead.', ); return '`'; } /** * {@inheritDoc} */ public function getRegexpExpression() { return 'RLIKE'; } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'LOCATE(' . $substr . ', ' . $str . ')'; } return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')'; } /** * {@inheritDoc} */ public function getConcatExpression() { return sprintf('CONCAT(%s)', implode(', ', func_get_args())); } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { $function = $operator === '+' ? 'DATE_ADD' : 'DATE_SUB'; return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit . ')'; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')'; } public function getCurrentDatabaseExpression(): string { return 'DATABASE()'; } /** * {@inheritDoc} */ public function getLengthExpression($column) { return 'CHAR_LENGTH(' . $column . ')'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListDatabasesSQL() { return 'SHOW DATABASES'; } /** * @deprecated * * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { return 'SHOW INDEX FROM ' . $table; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} * * Two approaches to listing the table indexes. The information_schema is * preferred, because it doesn't cause problems with SQL keywords such as "order" or "table". */ public function getListTableIndexesSQL($table, $database = null) { if ($database !== null) { return 'SELECT 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 WHERE TABLE_NAME = ' . $this->quoteStringLiteral($table) . ' AND TABLE_SCHEMA = ' . $this->quoteStringLiteral($database) . ' ORDER BY SEQ_IN_INDEX ASC'; } return 'SHOW INDEX FROM ' . $table; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return 'SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = ' . $this->quoteStringLiteral($database); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { // 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 return 'SELECT k.CONSTRAINT_NAME, k.COLUMN_NAME, k.REFERENCED_TABLE_NAME, ' . 'k.REFERENCED_COLUMN_NAME /*!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 */ ' . 'WHERE k.TABLE_NAME = ' . $this->quoteStringLiteral($table) . ' ' . 'AND k.TABLE_SCHEMA = ' . $this->getDatabaseNameSQL($database) . ' /*!50116 ' . 'AND c.CONSTRAINT_SCHEMA = ' . $this->getDatabaseNameSQL($database) . ' */' . 'ORDER BY k.ORDINAL_POSITION'; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default string column length on MySQL is deprecated' . ', specify the length explicitly.', ); } return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length on MySQL is deprecated' . ', specify the length explicitly.', ); } return $fixed ? 'BINARY(' . ($length > 0 ? $length : 255) . ')' : 'VARBINARY(' . ($length > 0 ? $length : 255) . ')'; } /** * Gets the SQL snippet used to declare a CLOB column type. * TINYTEXT : 2 ^ 8 - 1 = 255 * TEXT : 2 ^ 16 - 1 = 65535 * MEDIUMTEXT : 2 ^ 24 - 1 = 16777215 * LONGTEXT : 2 ^ 32 - 1 = 4294967295 * * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { if (! empty($column['length']) && is_numeric($column['length'])) { $length = $column['length']; if ($length <= static::LENGTH_LIMIT_TINYTEXT) { return 'TINYTEXT'; } if ($length <= static::LENGTH_LIMIT_TEXT) { return 'TEXT'; } if ($length <= static::LENGTH_LIMIT_MEDIUMTEXT) { return 'MEDIUMTEXT'; } } return 'LONGTEXT'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { if (isset($column['version']) && $column['version'] === true) { return 'TIMESTAMP'; } return 'DATETIME'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME'; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'TINYINT(1)'; } /** * {@inheritDoc} * * @deprecated * * MySQL prefers "autoincrement" identity columns since sequences can only * be emulated with a table. */ public function prefersIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/1519', 'AbstractMySQLPlatform::prefersIdentityColumns() is deprecated.', ); return true; } /** * {@inheritDoc} * * MySQL supports this through AUTO_INCREMENT columns. */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsInlineColumnComments() { return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsColumnCollation() { return true; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { return "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { return 'SELECT COLUMN_NAME AS Field, COLUMN_TYPE AS Type, IS_NULLABLE AS `Null`, ' . 'COLUMN_KEY AS `Key`, COLUMN_DEFAULT AS `Default`, EXTRA AS Extra, COLUMN_COMMENT AS Comment, ' . 'CHARACTER_SET_NAME AS CharacterSet, COLLATION_NAME AS Collation ' . 'FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ' . $this->getDatabaseNameSQL($database) . ' AND TABLE_NAME = ' . $this->quoteStringLiteral($table) . ' ORDER BY ORDINAL_POSITION ASC'; } /** * @deprecated Use {@see getColumnTypeSQLSnippet()} instead. * * The SQL snippets required to elucidate a column type * * Returns an array of the form [column type SELECT snippet, additional JOIN statement snippet] * * @return array{string, string} */ public function getColumnTypeSQLSnippets(string $tableAlias = 'c'): array { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6202', 'AbstractMySQLPlatform::getColumnTypeSQLSnippets() is deprecated. ' . 'Use AbstractMySQLPlatform::getColumnTypeSQLSnippet() instead.', ); return [$this->getColumnTypeSQLSnippet(...func_get_args()), '']; } /** * The SQL snippet required to elucidate a column type * * Returns a column type SELECT snippet string */ public function getColumnTypeSQLSnippet(string $tableAlias = 'c', ?string $databaseName = null): string { return $tableAlias . '.COLUMN_TYPE'; } /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ public function getListTableMetadataSQL(string $table, ?string $database = null): string { return sprintf( <<<'SQL' SELECT t.ENGINE, t.AUTO_INCREMENT, t.TABLE_COMMENT, t.CREATE_OPTIONS, t.TABLE_COLLATION, ccsa.CHARACTER_SET_NAME FROM information_schema.TABLES t INNER JOIN information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` ccsa ON ccsa.COLLATION_NAME = t.TABLE_COLLATION WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = %s AND TABLE_NAME = %s SQL , $this->getDatabaseNameSQL($database), $this->quoteStringLiteral($table), ); } /** * {@inheritDoc} */ public function getCreateTablesSQL(array $tables): array { $sql = []; foreach ($tables as $table) { $sql = array_merge($sql, $this->getCreateTableWithoutForeignKeysSQL($table)); } foreach ($tables as $table) { if (! $table->hasOption('engine') || $this->engineSupportsForeignKeys($table->getOption('engine'))) { foreach ($table->getForeignKeys() as $foreignKey) { $sql[] = $this->getCreateForeignKeySQL( $foreignKey, $table->getQuotedName($this), ); } } elseif (count($table->getForeignKeys()) > 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5414', 'Relying on the DBAL not generating DDL for foreign keys on MySQL engines' . ' other than InnoDB is deprecated.' . ' Define foreign key constraints only if they are necessary.', ); } } return $sql; } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $constraintName => $definition) { $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition); } } // add all indexes if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $indexName => $definition) { $queryFields .= ', ' . $this->getIndexDeclarationSQL($indexName, $definition); } } // attach all primary keys if (isset($options['primary']) && ! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } $query = 'CREATE '; if (! empty($options['temporary'])) { $query .= 'TEMPORARY '; } $query .= 'TABLE ' . $name . ' (' . $queryFields . ') '; $query .= $this->buildTableOptions($options); $query .= $this->buildPartitionOptions($options); $sql = [$query]; // Propagate foreign key constraints only for InnoDB. if (isset($options['foreignKeys'])) { if (! isset($options['engine']) || $this->engineSupportsForeignKeys($options['engine'])) { foreach ($options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } elseif (count($options['foreignKeys']) > 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5414', 'Relying on the DBAL not generating DDL for foreign keys on MySQL engines' . ' other than InnoDB is deprecated.' . ' Define foreign key constraints only if they are necessary.', ); } } return $sql; } public function createSelectSQLBuilder(): SelectSQLBuilder { return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', null); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getDefaultValueDeclarationSQL($column) { // Unset the default value if the given column definition does not allow default values. if ($column['type'] instanceof TextType || $column['type'] instanceof BlobType) { $column['default'] = null; } return parent::getDefaultValueDeclarationSQL($column); } /** * Build SQL for table options * * @param mixed[] $options */ private function buildTableOptions(array $options): string { if (isset($options['table_options'])) { return $options['table_options']; } $tableOptions = []; // Charset if (! isset($options['charset'])) { $options['charset'] = 'utf8'; } $tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']); if (isset($options['collate'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5214', 'The "collate" option is deprecated in favor of "collation" and will be removed in 4.0.', ); $options['collation'] = $options['collate']; } // Collation if (! isset($options['collation'])) { $options['collation'] = $options['charset'] . '_unicode_ci'; } $tableOptions[] = $this->getColumnCollationDeclarationSQL($options['collation']); // Engine if (! isset($options['engine'])) { $options['engine'] = 'InnoDB'; } $tableOptions[] = sprintf('ENGINE = %s', $options['engine']); // Auto increment if (isset($options['auto_increment'])) { $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']); } // Comment if (isset($options['comment'])) { $tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($options['comment'])); } // Row format if (isset($options['row_format'])) { $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']); } return implode(' ', $tableOptions); } /** * Build SQL for partition options. * * @param mixed[] $options */ private function buildPartitionOptions(array $options): string { return isset($options['partition_options']) ? ' ' . $options['partition_options'] : ''; } private function engineSupportsForeignKeys(string $engine): bool { return strcasecmp(trim($engine), 'InnoDB') === 0; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $columnSql = []; $queryParts = []; $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of SQL that renames a table using %s is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $queryParts[] = 'RENAME TO ' . $newName->getQuotedName($this); } foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columnProperties = array_merge($column->toArray(), [ 'comment' => $this->getColumnComment($column), ]); $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL( $column->getQuotedName($this), $columnProperties, ); } foreach ($diff->getDroppedColumns() as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $queryParts[] = 'DROP ' . $column->getQuotedName($this); } foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $newColumn = $columnDiff->getNewColumn(); $newColumnProperties = array_merge($newColumn->toArray(), [ 'comment' => $this->getColumnComment($newColumn), ]); $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); $queryParts[] = 'CHANGE ' . $oldColumn->getQuotedName($this) . ' ' . $this->getColumnDeclarationSQL($newColumn->getQuotedName($this), $newColumnProperties); } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $columnProperties = array_merge($column->toArray(), [ 'comment' => $this->getColumnComment($column), ]); $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnProperties); } $addedIndexes = $this->indexAssetsByLowerCaseName($diff->getAddedIndexes()); $modifiedIndexes = $this->indexAssetsByLowerCaseName($diff->getModifiedIndexes()); $diffModified = false; if (isset($addedIndexes['primary'])) { $keyColumns = array_unique(array_values($addedIndexes['primary']->getColumns())); $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')'; unset($addedIndexes['primary']); $diffModified = true; } elseif (isset($modifiedIndexes['primary'])) { $addedColumns = $this->indexAssetsByLowerCaseName($diff->getAddedColumns()); // Necessary in case the new primary key includes a new auto_increment column foreach ($modifiedIndexes['primary']->getColumns() as $columnName) { if (isset($addedColumns[$columnName]) && $addedColumns[$columnName]->getAutoincrement()) { $keyColumns = array_unique(array_values($modifiedIndexes['primary']->getColumns())); $queryParts[] = 'DROP PRIMARY KEY'; $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')'; unset($modifiedIndexes['primary']); $diffModified = true; break; } } } if ($diffModified) { $diff = new TableDiff( $diff->name, $diff->getAddedColumns(), $diff->getModifiedColumns(), $diff->getDroppedColumns(), array_values($addedIndexes), array_values($modifiedIndexes), $diff->getDroppedIndexes(), $diff->getOldTable(), $diff->getAddedForeignKeys(), $diff->getModifiedForeignKeys(), $diff->getDroppedForeignKeys(), $diff->getRenamedColumns(), $diff->getRenamedIndexes(), ); } $sql = []; $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { if (count($queryParts) > 0) { $sql[] = 'ALTER TABLE ' . ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this) . ' ' . implode(', ', $queryParts); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff), ); } return array_merge($sql, $tableSql, $columnSql); } /** * {@inheritDoc} */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { $sql = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); foreach ($diff->getModifiedIndexes() as $changedIndex) { $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $changedIndex)); } foreach ($diff->getDroppedIndexes() as $droppedIndex) { $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $droppedIndex)); foreach ($diff->getAddedIndexes() as $addedIndex) { if ($droppedIndex->getColumns() !== $addedIndex->getColumns()) { continue; } $indexClause = 'INDEX ' . $addedIndex->getName(); if ($addedIndex->isPrimary()) { $indexClause = 'PRIMARY KEY'; } elseif ($addedIndex->isUnique()) { $indexClause = 'UNIQUE INDEX ' . $addedIndex->getName(); } $query = 'ALTER TABLE ' . $tableNameSQL . ' DROP INDEX ' . $droppedIndex->getName() . ', '; $query .= 'ADD ' . $indexClause; $query .= ' (' . $this->getIndexFieldDeclarationListSQL($addedIndex) . ')'; $sql[] = $query; $diff->unsetAddedIndex($addedIndex); $diff->unsetDroppedIndex($droppedIndex); break; } } $engine = 'INNODB'; $table = $diff->getOldTable(); if ($table !== null && $table->hasOption('engine')) { $engine = strtoupper(trim($table->getOption('engine'))); } // Suppress foreign key constraint propagation on non-supporting engines. if ($engine !== 'INNODB') { $diff->addedForeignKeys = []; $diff->changedForeignKeys = []; $diff->removedForeignKeys = []; } $sql = array_merge( $sql, $this->getPreAlterTableAlterIndexForeignKeySQL($diff), parent::getPreAlterTableIndexForeignKeySQL($diff), $this->getPreAlterTableRenameIndexForeignKeySQL($diff), ); return $sql; } /** * @return string[] * * @throws Exception */ private function getPreAlterTableAlterPrimaryKeySQL(TableDiff $diff, Index $index): array { if (! $index->isPrimary()) { return []; } $table = $diff->getOldTable(); if ($table === null) { return []; } $sql = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); // Dropping primary keys requires to unset autoincrement attribute on the particular column first. foreach ($index->getColumns() as $columnName) { if (! $table->hasColumn($columnName)) { continue; } $column = $table->getColumn($columnName); if ($column->getAutoincrement() !== true) { continue; } $column->setAutoincrement(false); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); // original autoincrement information might be needed later on by other parts of the table alteration $column->setAutoincrement(true); } return $sql; } /** * @param TableDiff $diff The table diff to gather the SQL for. * * @return string[] * * @throws Exception */ private function getPreAlterTableAlterIndexForeignKeySQL(TableDiff $diff): array { $table = $diff->getOldTable(); if ($table === null) { return []; } $primaryKey = $table->getPrimaryKey(); if ($primaryKey === null) { return []; } $primaryKeyColumns = []; foreach ($primaryKey->getColumns() as $columnName) { if (! $table->hasColumn($columnName)) { continue; } $primaryKeyColumns[] = $table->getColumn($columnName); } if (count($primaryKeyColumns) === 0) { return []; } $sql = []; $tableNameSQL = $table->getQuotedName($this); foreach ($diff->getModifiedIndexes() as $changedIndex) { // Changed primary key if (! $changedIndex->isPrimary()) { continue; } foreach ($primaryKeyColumns as $column) { // Check if an autoincrement column was dropped from the primary key. if (! $column->getAutoincrement() || in_array($column->getName(), $changedIndex->getColumns(), true)) { continue; } // The autoincrement attribute needs to be removed from the dropped column // before we can drop and recreate the primary key. $column->setAutoincrement(false); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); // Restore the autoincrement attribute as it might be needed later on // by other parts of the table alteration. $column->setAutoincrement(true); } } return $sql; } /** * @param TableDiff $diff The table diff to gather the SQL for. * * @return string[] */ protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { $sql = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { if (in_array($foreignKey, $diff->getModifiedForeignKeys(), true)) { continue; } $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL); } return $sql; } /** * Returns the remaining foreign key constraints that require one of the renamed indexes. * * "Remaining" here refers to the diff between the foreign keys currently defined in the associated * table and the foreign keys to be removed. * * @param TableDiff $diff The table diff to evaluate. * * @return ForeignKeyConstraint[] */ private function getRemainingForeignKeyConstraintsRequiringRenamedIndexes(TableDiff $diff): array { if (count($diff->getRenamedIndexes()) === 0) { return []; } $table = $diff->getOldTable(); if ($table === null) { return []; } $foreignKeys = []; /** @var ForeignKeyConstraint[] $remainingForeignKeys */ $remainingForeignKeys = array_diff_key( $table->getForeignKeys(), $diff->getDroppedForeignKeys(), ); foreach ($remainingForeignKeys as $foreignKey) { foreach ($diff->getRenamedIndexes() as $index) { if ($foreignKey->intersectsIndexColumns($index)) { $foreignKeys[] = $foreignKey; break; } } } return $foreignKeys; } /** * {@inheritDoc} */ protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) { return array_merge( parent::getPostAlterTableIndexForeignKeySQL($diff), $this->getPostAlterTableRenameIndexForeignKeySQL($diff), ); } /** * @param TableDiff $diff The table diff to gather the SQL for. * * @return string[] */ protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { $sql = []; $newName = $diff->getNewName(); if ($newName !== false) { $tableNameSQL = $newName->getQuotedName($this); } else { $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); } foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { if (in_array($foreignKey, $diff->getModifiedForeignKeys(), true)) { continue; } $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL); } return $sql; } /** * {@inheritDoc} */ protected function getCreateIndexSQLFlags(Index $index) { $type = ''; if ($index->isUnique()) { $type .= 'UNIQUE '; } elseif ($index->hasFlag('fulltext')) { $type .= 'FULLTEXT '; } elseif ($index->hasFlag('spatial')) { $type .= 'SPATIAL '; } return $type; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getFloatDeclarationSQL(array $column) { return 'DOUBLE PRECISION' . $this->getUnsignedDeclaration($column); } /** * {@inheritDoc} */ public function getDecimalTypeDeclarationSQL(array $column) { return parent::getDecimalTypeDeclarationSQL($column) . $this->getUnsignedDeclaration($column); } /** * Get unsigned declaration for a column. * * @param mixed[] $columnDef */ private function getUnsignedDeclaration(array $columnDef): string { return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : ''; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { $autoinc = ''; if (! empty($column['autoincrement'])) { $autoinc = ' AUTO_INCREMENT'; } return $this->getUnsignedDeclaration($column) . $autoinc; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getColumnCharsetDeclarationSQL($charset) { return 'CHARACTER SET ' . $charset; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = ''; if ($foreignKey->hasOption('match')) { $query .= ' MATCH ' . $foreignKey->getOption('match'); } $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); return $query; } /** * {@inheritDoc} */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $indexName = $index->getQuotedName($this); } elseif (is_string($index)) { $indexName = $index; } else { throw new InvalidArgumentException( __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.', ); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } elseif (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', ); } if ($index instanceof Index && $index->isPrimary()) { // MySQL primary keys are always named "PRIMARY", // so we cannot use them in statements because of them being keyword. return $this->getDropPrimaryKeySQL($table); } return 'DROP INDEX ' . $indexName . ' ON ' . $table; } /** * @param string $table * * @return string */ protected function getDropPrimaryKeySQL($table) { return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'; } /** * The `ALTER TABLE ... DROP CONSTRAINT` syntax is only available as of MySQL 8.0.19. * * @link https://dev.mysql.com/doc/refman/8.0/en/alter-table.html */ public function getDropUniqueConstraintSQL(string $name, string $tableName): string { return $this->getDropIndexSQL($name, $tableName); } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4749', 'AbstractMySQLPlatform::getName() is deprecated. Identify platforms by their class.', ); return 'mysql'; } /** * {@inheritDoc} */ public function getReadLockSQL() { return 'LOCK IN SHARE MODE'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'bigint' => Types::BIGINT, 'binary' => Types::BINARY, 'blob' => Types::BLOB, 'char' => Types::STRING, 'date' => Types::DATE_MUTABLE, 'datetime' => Types::DATETIME_MUTABLE, 'decimal' => Types::DECIMAL, 'double' => Types::FLOAT, 'float' => Types::FLOAT, 'int' => Types::INTEGER, 'integer' => Types::INTEGER, 'longblob' => Types::BLOB, 'longtext' => Types::TEXT, 'mediumblob' => Types::BLOB, 'mediumint' => Types::INTEGER, 'mediumtext' => Types::TEXT, 'numeric' => Types::DECIMAL, 'real' => Types::FLOAT, 'set' => Types::SIMPLE_ARRAY, 'smallint' => Types::SMALLINT, 'string' => Types::STRING, 'text' => Types::TEXT, 'time' => Types::TIME_MUTABLE, 'timestamp' => Types::DATETIME_MUTABLE, 'tinyblob' => Types::BLOB, 'tinyint' => Types::BOOLEAN, 'tinytext' => Types::TEXT, 'varbinary' => Types::BINARY, 'varchar' => Types::STRING, 'year' => Types::DATE_MUTABLE, ]; } /** * {@inheritDoc} * * @deprecated */ public function getVarcharMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'AbstractMySQLPlatform::getVarcharMaxLength() is deprecated.', ); return 65535; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'AbstractMySQLPlatform::getBinaryMaxLength() is deprecated.', ); return 65535; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'AbstractMySQLPlatform::getReservedKeywordsClass() is deprecated,' . ' use AbstractMySQLPlatform::createReservedKeywordsList() instead.', ); return Keywords\MySQLKeywords::class; } /** * {@inheritDoc} * * MySQL commits a transaction implicitly when DROP TABLE is executed, however not * if DROP TEMPORARY TABLE is executed. */ public function getDropTemporaryTableSQL($table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } elseif (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', ); } return 'DROP TEMPORARY TABLE ' . $table; } /** * Gets the SQL Snippet used to declare a BLOB column type. * TINYBLOB : 2 ^ 8 - 1 = 255 * BLOB : 2 ^ 16 - 1 = 65535 * MEDIUMBLOB : 2 ^ 24 - 1 = 16777215 * LONGBLOB : 2 ^ 32 - 1 = 4294967295 * * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { if (! empty($column['length']) && is_numeric($column['length'])) { $length = $column['length']; if ($length <= static::LENGTH_LIMIT_TINYBLOB) { return 'TINYBLOB'; } if ($length <= static::LENGTH_LIMIT_BLOB) { return 'BLOB'; } if ($length <= static::LENGTH_LIMIT_MEDIUMBLOB) { return 'MEDIUMBLOB'; } } return 'LONGBLOB'; } /** * {@inheritDoc} */ public function quoteStringLiteral($str) { $str = str_replace('\\', '\\\\', $str); // MySQL requires backslashes to be escaped return parent::quoteStringLiteral($str); } /** * {@inheritDoc} */ public function getDefaultTransactionIsolationLevel() { return TransactionIsolationLevel::REPEATABLE_READ; } public function supportsColumnLengthIndexes(): bool { return true; } /** @deprecated Will be removed without replacement. */ protected function getDatabaseNameSQL(?string $databaseName): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6215', '%s is deprecated without replacement.', __METHOD__, ); if ($databaseName !== null) { return $this->quoteStringLiteral($databaseName); } return $this->getCurrentDatabaseExpression(); } public function createSchemaManager(Connection $connection): MySQLSchemaManager { return new MySQLSchemaManager($connection, $this); } /** * @param list<T> $assets * * @return array<string,T> * * @template T of AbstractAsset */ private function indexAssetsByLowerCaseName(array $assets): array { $result = []; foreach ($assets as $asset) { $result[strtolower($asset->getName())] = $asset; } return $result; } public function fetchTableOptionsByTable(bool $includeTableName): string { $sql = <<<'SQL' SELECT t.TABLE_NAME, t.ENGINE, t.AUTO_INCREMENT, t.TABLE_COMMENT, t.CREATE_OPTIONS, t.TABLE_COLLATION, ccsa.CHARACTER_SET_NAME FROM information_schema.TABLES t INNER JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY ccsa ON ccsa.COLLATION_NAME = t.TABLE_COLLATION SQL; $conditions = ['t.TABLE_SCHEMA = ?']; if ($includeTableName) { $conditions[] = 't.TABLE_NAME = ?'; } $conditions[] = "t.TABLE_TYPE = 'BASE TABLE'"; return $sql . ' WHERE ' . implode(' AND ', $conditions); } } dbal/src/Platforms/PostgreSQL120Platform.php 0000644 00000001752 15021222234 0014646 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms; /** * Provides the behavior, features and SQL dialect of the PostgreSQL 12.0 database platform. */ class PostgreSQL120Platform extends PostgreSQL100Platform { public function getDefaultColumnValueSQLSnippet(): string { // in case of GENERATED ALWAYS AS (foobar) STORED column (added in PostgreSQL 12.0) // PostgreSQL's pg_get_expr(adbin, adrelid) will return the 'foobar' part // which is not the 'default' value of the column but its 'definition' // so in that case we force it to NULL as DBAL will use that column only for the // 'default' value return <<<'SQL' SELECT CASE WHEN a.attgenerated = 's' THEN NULL ELSE pg_get_expr(adbin, adrelid) END FROM pg_attrdef WHERE c.oid = pg_attrdef.adrelid AND pg_attrdef.adnum=a.attnum SQL; } } dbal/src/Platforms/MariaDBPlatform.php 0000644 00000003163 15021222234 0013655 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; /** * Provides the behavior, features and SQL dialect of the MariaDB database platform of the oldest supported version. */ class MariaDBPlatform extends MySQLPlatform { /** * {@inheritDoc} * * Hop over the {@see AbstractMySQLPlatform} implementation until 4.0.x * where {@see MariaDBPlatform} no longer extends {@see MySQLPlatform}. * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getDefaultValueDeclarationSQL($column) { return AbstractPlatform::getDefaultValueDeclarationSQL($column); } /** * {@inheritDoc} * * @link https://mariadb.com/kb/en/library/json-data-type/ */ public function getJsonTypeDeclarationSQL(array $column): string { return 'LONGTEXT'; } /** @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass(): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'MariaDb1027Platform::getReservedKeywordsClass() is deprecated,' . ' use MariaDb1027Platform::createReservedKeywordsList() instead.', ); return Keywords\MariaDb102Keywords::class; } protected function initializeDoctrineTypeMappings(): void { parent::initializeDoctrineTypeMappings(); $this->doctrineTypeMapping['json'] = Types::JSON; } } dbal/src/Platforms/SQLServer2012Platform.php 0000644 00000000452 15021222234 0014547 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; /** * Provides the behavior, features and SQL dialect of the Microsoft SQL Server database platform * of the oldest supported version. * * @deprecated Use {@see SQLServerPlatform} instead. */ class SQLServer2012Platform extends SQLServerPlatform { } dbal/src/Platforms/MySQL57Platform.php 0000644 00000004515 15021222234 0013541 0 ustar 00 <?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; } } dbal/src/Platforms/DateIntervalUnit.php 0000644 00000000712 15021222234 0014130 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms; final class DateIntervalUnit { public const SECOND = 'SECOND'; public const MINUTE = 'MINUTE'; public const HOUR = 'HOUR'; public const DAY = 'DAY'; public const WEEK = 'WEEK'; public const MONTH = 'MONTH'; public const QUARTER = 'QUARTER'; public const YEAR = 'YEAR'; /** @codeCoverageIgnore */ private function __construct() { } } dbal/src/Platforms/MariaDb1060Platform.php 0000644 00000000576 15021222234 0014231 0 ustar 00 <?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(); } } dbal/src/Platforms/AbstractPlatform.php 0000644 00000422137 15021222234 0014167 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\Common\EventManager; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Event\SchemaAlterTableAddColumnEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableChangeColumnEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableRenameColumnEventArgs; use Doctrine\DBAL\Event\SchemaCreateTableColumnEventArgs; use Doctrine\DBAL\Event\SchemaCreateTableEventArgs; use Doctrine\DBAL\Event\SchemaDropTableEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\InvalidLockMode; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Platforms\Keywords\KeywordList; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\Constraint; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\SchemaDiff; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\Schema\UniqueConstraint; use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\SQL\Parser; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use UnexpectedValueException; use function addcslashes; use function array_map; use function array_merge; use function array_unique; use function array_values; use function assert; use function count; use function explode; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function in_array; use function is_array; use function is_bool; use function is_int; use function is_string; use function preg_quote; use function preg_replace; use function sprintf; use function str_replace; use function strlen; use function strpos; use function strtolower; use function strtoupper; /** * Base class for all DatabasePlatforms. The DatabasePlatforms are the central * point of abstraction of platform-specific behaviors, features and SQL dialects. * They are a passive source of information. * * @todo Remove any unnecessary methods. */ abstract class AbstractPlatform { public const CREATE_INDEXES = 1; public const CREATE_FOREIGNKEYS = 2; /** @var string[]|null */ protected $doctrineTypeMapping; /** * Contains a list of all columns that should generate parseable column comments for type-detection * in reverse engineering scenarios. * * @deprecated This property is deprecated and will be removed in Doctrine DBAL 4.0. * * @var string[]|null */ protected $doctrineTypeComments; /** * @deprecated * * @var EventManager|null */ protected $_eventManager; /** * Holds the KeywordList instance for the current platform. * * @var KeywordList|null */ protected $_keywords; private bool $disableTypeComments = false; /** @internal */ final public function setDisableTypeComments(bool $value): void { $this->disableTypeComments = $value; } /** * Sets the EventManager used by the Platform. * * @deprecated * * @return void */ public function setEventManager(EventManager $eventManager) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', '%s is deprecated.', __METHOD__, ); $this->_eventManager = $eventManager; } /** * Gets the EventManager used by the Platform. * * @deprecated * * @return EventManager|null */ public function getEventManager() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', '%s is deprecated.', __METHOD__, ); return $this->_eventManager; } /** * Returns the SQL snippet that declares a boolean column. * * @param mixed[] $column * * @return string */ abstract public function getBooleanTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares a 4 byte integer column. * * @param mixed[] $column * * @return string */ abstract public function getIntegerTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares an 8 byte integer column. * * @param mixed[] $column * * @return string */ abstract public function getBigIntTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares a 2 byte integer column. * * @param mixed[] $column * * @return string */ abstract public function getSmallIntTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares common properties of an integer column. * * @param mixed[] $column * * @return string */ abstract protected function _getCommonIntegerTypeDeclarationSQL(array $column); /** * Lazy load Doctrine Type Mappings. * * @return void */ abstract protected function initializeDoctrineTypeMappings(); /** * Initializes Doctrine Type Mappings with the platform defaults * and with all additional type mappings. */ private function initializeAllDoctrineTypeMappings(): void { $this->initializeDoctrineTypeMappings(); foreach (Type::getTypesMap() as $typeName => $className) { foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) { $dbType = strtolower($dbType); $this->doctrineTypeMapping[$dbType] = $typeName; } } } /** * Returns the SQL snippet used to declare a column that can * store characters in the ASCII character set * * @param mixed[] $column */ public function getAsciiStringTypeDeclarationSQL(array $column): string { return $this->getStringTypeDeclarationSQL($column); } /** * Returns the SQL snippet used to declare a VARCHAR column type. * * @deprecated Use {@link getStringTypeDeclarationSQL()} instead. * * @param mixed[] $column * * @return string */ public function getVarcharTypeDeclarationSQL(array $column) { if (isset($column['length'])) { $lengthOmitted = false; } else { $column['length'] = $this->getVarcharDefaultLength(); $lengthOmitted = true; } $fixed = $column['fixed'] ?? false; $maxLength = $fixed ? $this->getCharMaxLength() : $this->getVarcharMaxLength(); if ($column['length'] > $maxLength) { return $this->getClobTypeDeclarationSQL($column); } return $this->getVarcharTypeDeclarationSQLSnippet($column['length'], $fixed, $lengthOmitted); } /** * Returns the SQL snippet used to declare a string column type. * * @param mixed[] $column * * @return string */ public function getStringTypeDeclarationSQL(array $column) { return $this->getVarcharTypeDeclarationSQL($column); } /** * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. * * @param mixed[] $column The column definition. * * @return string */ public function getBinaryTypeDeclarationSQL(array $column) { if (isset($column['length'])) { $lengthOmitted = false; } else { $column['length'] = $this->getBinaryDefaultLength(); $lengthOmitted = true; } $fixed = $column['fixed'] ?? false; $maxLength = $this->getBinaryMaxLength(); if ($column['length'] > $maxLength) { if ($maxLength > 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3187', 'Binary column length %d is greater than supported by the platform (%d).' . ' Reduce the column length or use a BLOB column instead.', $column['length'], $maxLength, ); } return $this->getBlobTypeDeclarationSQL($column); } return $this->getBinaryTypeDeclarationSQLSnippet($column['length'], $fixed, $lengthOmitted); } /** * Returns the SQL snippet to declare a GUID/UUID column. * * By default this maps directly to a CHAR(36) and only maps to more * special datatypes when the underlying databases support this datatype. * * @param mixed[] $column * * @return string */ public function getGuidTypeDeclarationSQL(array $column) { $column['length'] = 36; $column['fixed'] = true; return $this->getStringTypeDeclarationSQL($column); } /** * Returns the SQL snippet to declare a JSON column. * * By default this maps directly to a CLOB and only maps to more * special datatypes when the underlying databases support this datatype. * * @param mixed[] $column * * @return string */ public function getJsonTypeDeclarationSQL(array $column) { return $this->getClobTypeDeclarationSQL($column); } /** * @param int|false $length * @param bool $fixed * * @return string * * @throws Exception If not supported on this platform. */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { throw Exception::notSupported('VARCHARs not supported by Platform.'); } /** * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. * * @param int|false $length The length of the column. * @param bool $fixed Whether the column length is fixed. * * @return string * * @throws Exception If not supported on this platform. */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { throw Exception::notSupported('BINARY/VARBINARY column types are not supported by this platform.'); } /** * Returns the SQL snippet used to declare a CLOB column type. * * @param mixed[] $column * * @return string */ abstract public function getClobTypeDeclarationSQL(array $column); /** * Returns the SQL Snippet used to declare a BLOB column type. * * @param mixed[] $column * * @return string */ abstract public function getBlobTypeDeclarationSQL(array $column); /** * Gets the name of the platform. * * @deprecated Identify platforms by their class. * * @return string */ abstract public function getName(); /** * Registers a doctrine type to be used in conjunction with a column type of this platform. * * @param string $dbType * @param string $doctrineType * * @return void * * @throws Exception If the type is not found. */ public function registerDoctrineTypeMapping($dbType, $doctrineType) { if ($this->doctrineTypeMapping === null) { $this->initializeAllDoctrineTypeMappings(); } if (! Types\Type::hasType($doctrineType)) { throw Exception::typeNotFound($doctrineType); } $dbType = strtolower($dbType); $this->doctrineTypeMapping[$dbType] = $doctrineType; $doctrineType = Type::getType($doctrineType); if (! $doctrineType->requiresSQLCommentHint($this)) { return; } $this->markDoctrineTypeCommented($doctrineType); } /** * Gets the Doctrine type that is mapped for the given database column type. * * @param string $dbType * * @return string * * @throws Exception */ public function getDoctrineTypeMapping($dbType) { if ($this->doctrineTypeMapping === null) { $this->initializeAllDoctrineTypeMappings(); } $dbType = strtolower($dbType); if (! isset($this->doctrineTypeMapping[$dbType])) { throw new Exception( 'Unknown database type ' . $dbType . ' requested, ' . static::class . ' may not support it.', ); } return $this->doctrineTypeMapping[$dbType]; } /** * Checks if a database type is currently supported by this platform. * * @param string $dbType * * @return bool */ public function hasDoctrineTypeMappingFor($dbType) { if ($this->doctrineTypeMapping === null) { $this->initializeAllDoctrineTypeMappings(); } $dbType = strtolower($dbType); return isset($this->doctrineTypeMapping[$dbType]); } /** * Initializes the Doctrine Type comments instance variable for in_array() checks. * * @deprecated This API will be removed in Doctrine DBAL 4.0. * * @return void */ protected function initializeCommentedDoctrineTypes() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5058', '%s is deprecated and will be removed in Doctrine DBAL 4.0.', __METHOD__, ); $this->doctrineTypeComments = []; foreach (Type::getTypesMap() as $typeName => $className) { $type = Type::getType($typeName); if (! $type->requiresSQLCommentHint($this)) { continue; } $this->doctrineTypeComments[] = $typeName; } } /** * Is it necessary for the platform to add a parsable type comment to allow reverse engineering the given type? * * @deprecated Use {@link Type::requiresSQLCommentHint()} instead. * * @return bool */ public function isCommentedDoctrineType(Type $doctrineType) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5058', '%s is deprecated and will be removed in Doctrine DBAL 4.0. Use Type::requiresSQLCommentHint() instead.', __METHOD__, ); if ($this->doctrineTypeComments === null) { $this->initializeCommentedDoctrineTypes(); } return $doctrineType->requiresSQLCommentHint($this); } /** * Marks this type as to be commented in ALTER TABLE and CREATE TABLE statements. * * @param string|Type $doctrineType * * @return void */ public function markDoctrineTypeCommented($doctrineType) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5058', '%s is deprecated and will be removed in Doctrine DBAL 4.0. Use Type::requiresSQLCommentHint() instead.', __METHOD__, ); if ($this->doctrineTypeComments === null) { $this->initializeCommentedDoctrineTypes(); } assert(is_array($this->doctrineTypeComments)); $this->doctrineTypeComments[] = $doctrineType instanceof Type ? $doctrineType->getName() : $doctrineType; } /** * Gets the comment to append to a column comment that helps parsing this type in reverse engineering. * * @deprecated This method will be removed without replacement. * * @return string */ public function getDoctrineTypeComment(Type $doctrineType) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5107', '%s is deprecated and will be removed in Doctrine DBAL 4.0.', __METHOD__, ); return '(DC2Type:' . $doctrineType->getName() . ')'; } /** * Gets the comment of a passed column modified by potential doctrine type comment hints. * * @deprecated This method will be removed without replacement. * * @return string|null */ protected function getColumnComment(Column $column) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5107', '%s is deprecated and will be removed in Doctrine DBAL 4.0.', __METHOD__, ); $comment = $column->getComment(); if (! $this->disableTypeComments && $column->getType()->requiresSQLCommentHint($this)) { $comment .= $this->getDoctrineTypeComment($column->getType()); } return $comment; } /** * Gets the character used for identifier quoting. * * @deprecated Use {@see quoteIdentifier()} to quote identifiers instead. * * @return string */ public function getIdentifierQuoteCharacter() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5388', 'AbstractPlatform::getIdentifierQuoteCharacter() is deprecated. Use quoteIdentifier() instead.', ); return '"'; } /** * Gets the string portion that starts an SQL comment. * * @deprecated * * @return string */ public function getSqlCommentStartString() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getSqlCommentStartString() is deprecated.', ); return '--'; } /** * Gets the string portion that ends an SQL comment. * * @deprecated * * @return string */ public function getSqlCommentEndString() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getSqlCommentEndString() is deprecated.', ); return "\n"; } /** * Gets the maximum length of a char column. * * @deprecated */ public function getCharMaxLength(): int { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'AbstractPlatform::getCharMaxLength() is deprecated.', ); return $this->getVarcharMaxLength(); } /** * Gets the maximum length of a varchar column. * * @deprecated * * @return int */ public function getVarcharMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'AbstractPlatform::getVarcharMaxLength() is deprecated.', ); return 4000; } /** * Gets the default length of a varchar column. * * @deprecated * * @return int */ public function getVarcharDefaultLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default varchar column length is deprecated, specify the length explicitly.', ); return 255; } /** * Gets the maximum length of a binary column. * * @deprecated * * @return int */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'AbstractPlatform::getBinaryMaxLength() is deprecated.', ); return 4000; } /** * Gets the default length of a binary column. * * @deprecated * * @return int */ public function getBinaryDefaultLength() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length is deprecated, specify the length explicitly.', ); return 255; } /** * Gets all SQL wildcard characters of the platform. * * @deprecated Use {@see AbstractPlatform::getLikeWildcardCharacters()} instead. * * @return string[] */ public function getWildcards() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getWildcards() is deprecated.' . ' Use AbstractPlatform::getLikeWildcardCharacters() instead.', ); return ['%', '_']; } /** * Returns the regular expression operator. * * @return string * * @throws Exception If not supported on this platform. */ public function getRegexpExpression() { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL snippet to get the average value of a column. * * @deprecated Use AVG() in SQL instead. * * @param string $column The column to use. * * @return string Generated SQL including an AVG aggregate function. */ public function getAvgExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getAvgExpression() is deprecated. Use AVG() in SQL instead.', ); return 'AVG(' . $column . ')'; } /** * Returns the SQL snippet to get the number of rows (without a NULL value) of a column. * * If a '*' is used instead of a column the number of selected rows is returned. * * @deprecated Use COUNT() in SQL instead. * * @param string|int $column The column to use. * * @return string Generated SQL including a COUNT aggregate function. */ public function getCountExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getCountExpression() is deprecated. Use COUNT() in SQL instead.', ); return 'COUNT(' . $column . ')'; } /** * Returns the SQL snippet to get the highest value of a column. * * @deprecated Use MAX() in SQL instead. * * @param string $column The column to use. * * @return string Generated SQL including a MAX aggregate function. */ public function getMaxExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getMaxExpression() is deprecated. Use MAX() in SQL instead.', ); return 'MAX(' . $column . ')'; } /** * Returns the SQL snippet to get the lowest value of a column. * * @deprecated Use MIN() in SQL instead. * * @param string $column The column to use. * * @return string Generated SQL including a MIN aggregate function. */ public function getMinExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getMinExpression() is deprecated. Use MIN() in SQL instead.', ); return 'MIN(' . $column . ')'; } /** * Returns the SQL snippet to get the total sum of a column. * * @deprecated Use SUM() in SQL instead. * * @param string $column The column to use. * * @return string Generated SQL including a SUM aggregate function. */ public function getSumExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getSumExpression() is deprecated. Use SUM() in SQL instead.', ); return 'SUM(' . $column . ')'; } // scalar functions /** * Returns the SQL snippet to get the md5 sum of a column. * * Note: Not SQL92, but common functionality. * * @deprecated * * @param string $column * * @return string */ public function getMd5Expression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getMd5Expression() is deprecated.', ); return 'MD5(' . $column . ')'; } /** * Returns the SQL snippet to get the length of a text column in characters. * * @param string $column * * @return string */ public function getLengthExpression($column) { return 'LENGTH(' . $column . ')'; } /** * Returns the SQL snippet to get the squared value of a column. * * @deprecated Use SQRT() in SQL instead. * * @param string $column The column to use. * * @return string Generated SQL including an SQRT aggregate function. */ public function getSqrtExpression($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getSqrtExpression() is deprecated. Use SQRT() in SQL instead.', ); return 'SQRT(' . $column . ')'; } /** * Returns the SQL snippet to round a numeric column to the number of decimals specified. * * @deprecated Use ROUND() in SQL instead. * * @param string $column * @param string|int $decimals * * @return string */ public function getRoundExpression($column, $decimals = 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getRoundExpression() is deprecated. Use ROUND() in SQL instead.', ); return 'ROUND(' . $column . ', ' . $decimals . ')'; } /** * Returns the SQL snippet to get the remainder of the division operation $expression1 / $expression2. * * @param string $expression1 * @param string $expression2 * * @return string */ public function getModExpression($expression1, $expression2) { return 'MOD(' . $expression1 . ', ' . $expression2 . ')'; } /** * Returns the SQL snippet to trim a string. * * @param string $str The expression to apply the trim to. * @param int $mode The position of the trim (leading/trailing/both). * @param string|bool $char The char to trim, has to be quoted already. Defaults to space. * * @return string */ public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) { $expression = ''; switch ($mode) { case TrimMode::LEADING: $expression = 'LEADING '; break; case TrimMode::TRAILING: $expression = 'TRAILING '; break; case TrimMode::BOTH: $expression = 'BOTH '; break; } if ($char !== false) { $expression .= $char . ' '; } if ($mode !== TrimMode::UNSPECIFIED || $char !== false) { $expression .= 'FROM '; } return 'TRIM(' . $expression . $str . ')'; } /** * Returns the SQL snippet to trim trailing space characters from the expression. * * @deprecated Use RTRIM() in SQL instead. * * @param string $str Literal string or column name. * * @return string */ public function getRtrimExpression($str) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getRtrimExpression() is deprecated. Use RTRIM() in SQL instead.', ); return 'RTRIM(' . $str . ')'; } /** * Returns the SQL snippet to trim leading space characters from the expression. * * @deprecated Use LTRIM() in SQL instead. * * @param string $str Literal string or column name. * * @return string */ public function getLtrimExpression($str) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getLtrimExpression() is deprecated. Use LTRIM() in SQL instead.', ); return 'LTRIM(' . $str . ')'; } /** * Returns the SQL snippet to change all characters from the expression to uppercase, * according to the current character set mapping. * * @deprecated Use UPPER() in SQL instead. * * @param string $str Literal string or column name. * * @return string */ public function getUpperExpression($str) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getUpperExpression() is deprecated. Use UPPER() in SQL instead.', ); return 'UPPER(' . $str . ')'; } /** * Returns the SQL snippet to change all characters from the expression to lowercase, * according to the current character set mapping. * * @deprecated Use LOWER() in SQL instead. * * @param string $str Literal string or column name. * * @return string */ public function getLowerExpression($str) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getLowerExpression() is deprecated. Use LOWER() in SQL instead.', ); return 'LOWER(' . $str . ')'; } /** * Returns the SQL snippet to get the position of the first occurrence of substring $substr in string $str. * * @param string $str Literal string. * @param string $substr Literal string to find. * @param string|int|false $startPos Position to start at, beginning of string by default. * * @return string * * @throws Exception If not supported on this platform. */ public function getLocateExpression($str, $substr, $startPos = false) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL snippet to get the current system date. * * @deprecated Generate dates within the application. * * @return string */ public function getNowExpression() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4753', 'AbstractPlatform::getNowExpression() is deprecated. Generate dates within the application.', ); return 'NOW()'; } /** * Returns a SQL snippet to get a substring inside an SQL statement. * * Note: Not SQL92, but common functionality. * * SQLite only supports the 2 parameter variant of this function. * * @param string $string An sql string literal or column name/alias. * @param string|int $start Where to start the substring portion. * @param string|int|null $length The substring portion length. * * @return string */ public function getSubstringExpression($string, $start, $length = null) { if ($length === null) { return 'SUBSTRING(' . $string . ' FROM ' . $start . ')'; } return 'SUBSTRING(' . $string . ' FROM ' . $start . ' FOR ' . $length . ')'; } /** * Returns a SQL snippet to concatenate the given expressions. * * Accepts an arbitrary number of string parameters. Each parameter must contain an expression. * * @return string */ public function getConcatExpression() { return implode(' || ', func_get_args()); } /** * Returns the SQL for a logical not. * * Example: * <code> * $q = new Doctrine_Query(); * $e = $q->expr; * $q->select('*')->from('table') * ->where($e->eq('id', $e->not('null')); * </code> * * @deprecated Use NOT() in SQL instead. * * @param string $expression * * @return string The logical expression. */ public function getNotExpression($expression) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getNotExpression() is deprecated. Use NOT() in SQL instead.', ); return 'NOT(' . $expression . ')'; } /** * Returns the SQL that checks if an expression is null. * * @deprecated Use IS NULL in SQL instead. * * @param string $expression The expression that should be compared to null. * * @return string The logical expression. */ public function getIsNullExpression($expression) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getIsNullExpression() is deprecated. Use IS NULL in SQL instead.', ); return $expression . ' IS NULL'; } /** * Returns the SQL that checks if an expression is not null. * * @deprecated Use IS NOT NULL in SQL instead. * * @param string $expression The expression that should be compared to null. * * @return string The logical expression. */ public function getIsNotNullExpression($expression) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getIsNotNullExpression() is deprecated. Use IS NOT NULL in SQL instead.', ); return $expression . ' IS NOT NULL'; } /** * Returns the SQL that checks if an expression evaluates to a value between two values. * * The parameter $expression is checked if it is between $value1 and $value2. * * Note: There is a slight difference in the way BETWEEN works on some databases. * http://www.w3schools.com/sql/sql_between.asp. If you want complete database * independence you should avoid using between(). * * @deprecated Use BETWEEN in SQL instead. * * @param string $expression The value to compare to. * @param string $value1 The lower value to compare with. * @param string $value2 The higher value to compare with. * * @return string The logical expression. */ public function getBetweenExpression($expression, $value1, $value2) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getBetweenExpression() is deprecated. Use BETWEEN in SQL instead.', ); return $expression . ' BETWEEN ' . $value1 . ' AND ' . $value2; } /** * Returns the SQL to get the arccosine of a value. * * @deprecated Use ACOS() in SQL instead. * * @param string $value * * @return string */ public function getAcosExpression($value) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getAcosExpression() is deprecated. Use ACOS() in SQL instead.', ); return 'ACOS(' . $value . ')'; } /** * Returns the SQL to get the sine of a value. * * @deprecated Use SIN() in SQL instead. * * @param string $value * * @return string */ public function getSinExpression($value) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getSinExpression() is deprecated. Use SIN() in SQL instead.', ); return 'SIN(' . $value . ')'; } /** * Returns the SQL to get the PI value. * * @deprecated Use PI() in SQL instead. * * @return string */ public function getPiExpression() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getPiExpression() is deprecated. Use PI() in SQL instead.', ); return 'PI()'; } /** * Returns the SQL to get the cosine of a value. * * @deprecated Use COS() in SQL instead. * * @param string $value * * @return string */ public function getCosExpression($value) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getCosExpression() is deprecated. Use COS() in SQL instead.', ); return 'COS(' . $value . ')'; } /** * Returns the SQL to calculate the difference in days between the two passed dates. * * Computes diff = date1 - date2. * * @param string $date1 * @param string $date2 * * @return string * * @throws Exception If not supported on this platform. */ public function getDateDiffExpression($date1, $date2) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to add the number of given seconds to a date. * * @param string $date * @param int|string $seconds * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddSecondsExpression($date, $seconds) { if (is_int($seconds)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $seconds as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $seconds, DateIntervalUnit::SECOND); } /** * Returns the SQL to subtract the number of given seconds from a date. * * @param string $date * @param int|string $seconds * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubSecondsExpression($date, $seconds) { if (is_int($seconds)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $seconds as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $seconds, DateIntervalUnit::SECOND); } /** * Returns the SQL to add the number of given minutes to a date. * * @param string $date * @param int|string $minutes * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddMinutesExpression($date, $minutes) { if (is_int($minutes)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $minutes as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $minutes, DateIntervalUnit::MINUTE); } /** * Returns the SQL to subtract the number of given minutes from a date. * * @param string $date * @param int|string $minutes * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubMinutesExpression($date, $minutes) { if (is_int($minutes)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $minutes as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $minutes, DateIntervalUnit::MINUTE); } /** * Returns the SQL to add the number of given hours to a date. * * @param string $date * @param int|string $hours * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddHourExpression($date, $hours) { if (is_int($hours)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $hours as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $hours, DateIntervalUnit::HOUR); } /** * Returns the SQL to subtract the number of given hours to a date. * * @param string $date * @param int|string $hours * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubHourExpression($date, $hours) { if (is_int($hours)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $hours as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $hours, DateIntervalUnit::HOUR); } /** * Returns the SQL to add the number of given days to a date. * * @param string $date * @param int|string $days * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddDaysExpression($date, $days) { if (is_int($days)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $days as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $days, DateIntervalUnit::DAY); } /** * Returns the SQL to subtract the number of given days to a date. * * @param string $date * @param int|string $days * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubDaysExpression($date, $days) { if (is_int($days)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $days as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $days, DateIntervalUnit::DAY); } /** * Returns the SQL to add the number of given weeks to a date. * * @param string $date * @param int|string $weeks * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddWeeksExpression($date, $weeks) { if (is_int($weeks)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $weeks as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $weeks, DateIntervalUnit::WEEK); } /** * Returns the SQL to subtract the number of given weeks from a date. * * @param string $date * @param int|string $weeks * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubWeeksExpression($date, $weeks) { if (is_int($weeks)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $weeks as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $weeks, DateIntervalUnit::WEEK); } /** * Returns the SQL to add the number of given months to a date. * * @param string $date * @param int|string $months * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddMonthExpression($date, $months) { if (is_int($months)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $months as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $months, DateIntervalUnit::MONTH); } /** * Returns the SQL to subtract the number of given months to a date. * * @param string $date * @param int|string $months * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubMonthExpression($date, $months) { if (is_int($months)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $months as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $months, DateIntervalUnit::MONTH); } /** * Returns the SQL to add the number of given quarters to a date. * * @param string $date * @param int|string $quarters * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddQuartersExpression($date, $quarters) { if (is_int($quarters)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $quarters as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $quarters, DateIntervalUnit::QUARTER); } /** * Returns the SQL to subtract the number of given quarters from a date. * * @param string $date * @param int|string $quarters * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubQuartersExpression($date, $quarters) { if (is_int($quarters)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $quarters as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $quarters, DateIntervalUnit::QUARTER); } /** * Returns the SQL to add the number of given years to a date. * * @param string $date * @param int|string $years * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddYearsExpression($date, $years) { if (is_int($years)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $years as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '+', $years, DateIntervalUnit::YEAR); } /** * Returns the SQL to subtract the number of given years from a date. * * @param string $date * @param int|string $years * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubYearsExpression($date, $years) { if (is_int($years)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3498', 'Passing $years as an integer is deprecated. Pass it as a numeric string instead.', ); } return $this->getDateArithmeticIntervalExpression($date, '-', $years, DateIntervalUnit::YEAR); } /** * Returns the SQL for a date arithmetic expression. * * @param string $date The column or literal representing a date * to perform the arithmetic operation on. * @param string $operator The arithmetic operator (+ or -). * @param int|string $interval The interval that shall be calculated into the date. * @param string $unit The unit of the interval that shall be calculated into the date. * One of the {@see DateIntervalUnit} constants. * * @return string * * @throws Exception If not supported on this platform. */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { throw Exception::notSupported(__METHOD__); } /** * Generates the SQL expression which represents the given date interval multiplied by a number * * @param string $interval SQL expression describing the interval value * @param int $multiplier Interval multiplier */ protected function multiplyInterval(string $interval, int $multiplier): string { return sprintf('(%s * %d)', $interval, $multiplier); } /** * Returns the SQL bit AND comparison expression. * * @param string $value1 * @param string $value2 * * @return string */ public function getBitAndComparisonExpression($value1, $value2) { return '(' . $value1 . ' & ' . $value2 . ')'; } /** * Returns the SQL bit OR comparison expression. * * @param string $value1 * @param string $value2 * * @return string */ public function getBitOrComparisonExpression($value1, $value2) { return '(' . $value1 . ' | ' . $value2 . ')'; } /** * Returns the SQL expression which represents the currently selected database. */ abstract public function getCurrentDatabaseExpression(): string; /** * Returns the FOR UPDATE expression. * * @deprecated This API is not portable. Use {@link QueryBuilder::forUpdate()}` instead. * * @return string */ public function getForUpdateSQL() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6191', '%s is deprecated as non-portable.', __METHOD__, ); return 'FOR UPDATE'; } /** * Honors that some SQL vendors such as MsSql use table hints for locking instead of the * ANSI SQL FOR UPDATE specification. * * @param string $fromClause The FROM clause to append the hint for the given lock mode to * @param int $lockMode One of the Doctrine\DBAL\LockMode::* constants * @psalm-param LockMode::* $lockMode */ public function appendLockHint(string $fromClause, int $lockMode): string { switch ($lockMode) { case LockMode::NONE: case LockMode::OPTIMISTIC: case LockMode::PESSIMISTIC_READ: case LockMode::PESSIMISTIC_WRITE: return $fromClause; default: throw InvalidLockMode::fromLockMode($lockMode); } } /** * Returns the SQL snippet to append to any SELECT statement which locks rows in shared read lock. * * This defaults to the ANSI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database * vendors allow to lighten this constraint up to be a real read lock. * * @deprecated This API is not portable. * * @return string */ public function getReadLockSQL() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6191', '%s is deprecated as non-portable.', __METHOD__, ); return $this->getForUpdateSQL(); } /** * Returns the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows. * * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ANSI SQL standard. * * @deprecated This API is not portable. * * @return string */ public function getWriteLockSQL() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6191', '%s is deprecated as non-portable.', __METHOD__, ); return $this->getForUpdateSQL(); } /** * Returns the SQL snippet to drop an existing table. * * @param Table|string $table * * @return string * * @throws InvalidArgumentException */ public function getDropTableSQL($table) { $tableArg = $table; if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } if (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', ); } if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaDropTable)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaDropTable, ); $eventArgs = new SchemaDropTableEventArgs($tableArg, $this); $this->_eventManager->dispatchEvent(Events::onSchemaDropTable, $eventArgs); if ($eventArgs->isDefaultPrevented()) { $sql = $eventArgs->getSql(); if ($sql === null) { throw new UnexpectedValueException('Default implementation of DROP TABLE was overridden with NULL'); } return $sql; } } return 'DROP TABLE ' . $table; } /** * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction. * * @param Table|string $table * * @return string */ public function getDropTemporaryTableSQL($table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } return $this->getDropTableSQL($table); } /** * Returns the SQL to drop an index from a table. * * @param Index|string $index * @param Table|string|null $table * * @return string * * @throws InvalidArgumentException */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $index = $index->getQuotedName($this); } elseif (! is_string($index)) { throw new InvalidArgumentException( __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.', ); } return 'DROP INDEX ' . $index; } /** * Returns the SQL to drop a constraint. * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. * * @param Constraint|string $constraint * @param Table|string $table * * @return string */ public function getDropConstraintSQL($constraint, $table) { if ($constraint instanceof Constraint) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $constraint as a Constraint object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); } else { $constraint = new Identifier($constraint); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); } else { $table = new Identifier($table); } $constraint = $constraint->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint; } /** * Returns the SQL to drop a foreign key. * * @param ForeignKeyConstraint|string $foreignKey * @param Table|string $table * * @return string */ public function getDropForeignKeySQL($foreignKey, $table) { if ($foreignKey instanceof ForeignKeyConstraint) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $foreignKey as a ForeignKeyConstraint object to %s is deprecated.' . ' Pass it as a quoted name instead.', __METHOD__, ); } else { $foreignKey = new Identifier($foreignKey); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); } else { $table = new Identifier($table); } $foreignKey = $foreignKey->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey; } /** * Returns the SQL to drop a unique constraint. */ public function getDropUniqueConstraintSQL(string $name, string $tableName): string { return $this->getDropConstraintSQL($name, $tableName); } /** * Returns the SQL statement(s) to create a table with the specified name, columns and constraints * on this platform. * * @param int $createFlags * @psalm-param int-mask-of<self::CREATE_*> $createFlags * * @return list<string> The list of SQL statements. * * @throws Exception * @throws InvalidArgumentException */ public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES) { if (! is_int($createFlags)) { throw new InvalidArgumentException( 'Second argument of AbstractPlatform::getCreateTableSQL() has to be integer.', ); } if (($createFlags & self::CREATE_INDEXES) === 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5416', 'Unsetting the CREATE_INDEXES flag in AbstractPlatform::getCreateTableSQL() is deprecated.', ); } if (($createFlags & self::CREATE_FOREIGNKEYS) === 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5416', 'Not setting the CREATE_FOREIGNKEYS flag in AbstractPlatform::getCreateTableSQL()' . ' is deprecated. In order to build the statements that create multiple tables' . ' referencing each other via foreign keys, use AbstractPlatform::getCreateTablesSQL().', ); } return $this->buildCreateTableSQL( $table, ($createFlags & self::CREATE_INDEXES) > 0, ($createFlags & self::CREATE_FOREIGNKEYS) > 0, ); } public function createSelectSQLBuilder(): SelectSQLBuilder { return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', 'SKIP LOCKED'); } /** * @internal * * @return list<string> * * @throws Exception */ final protected function getCreateTableWithoutForeignKeysSQL(Table $table): array { return $this->buildCreateTableSQL($table, true, false); } /** * @return list<string> * * @throws Exception */ private function buildCreateTableSQL(Table $table, bool $createIndexes, bool $createForeignKeys): array { if (count($table->getColumns()) === 0) { throw Exception::noColumnsSpecifiedForTable($table->getName()); } $tableName = $table->getQuotedName($this); $options = $table->getOptions(); $options['uniqueConstraints'] = []; $options['indexes'] = []; $options['primary'] = []; if ($createIndexes) { foreach ($table->getIndexes() as $index) { if (! $index->isPrimary()) { $options['indexes'][$index->getQuotedName($this)] = $index; continue; } $options['primary'] = $index->getQuotedColumns($this); $options['primary_index'] = $index; } foreach ($table->getUniqueConstraints() as $uniqueConstraint) { $options['uniqueConstraints'][$uniqueConstraint->getQuotedName($this)] = $uniqueConstraint; } } if ($createForeignKeys) { $options['foreignKeys'] = []; foreach ($table->getForeignKeys() as $fkConstraint) { $options['foreignKeys'][] = $fkConstraint; } } $columnSql = []; $columns = []; foreach ($table->getColumns() as $column) { if ( $this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn) ) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaCreateTableColumn, ); $eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this); $this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); if ($eventArgs->isDefaultPrevented()) { continue; } } $columnData = $this->columnToArray($column); if (in_array($column->getName(), $options['primary'], true)) { $columnData['primary'] = true; } $columns[$columnData['name']] = $columnData; } if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaCreateTable, ); $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this); $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs); if ($eventArgs->isDefaultPrevented()) { return array_merge($eventArgs->getSql(), $columnSql); } } $sql = $this->_getCreateTableSQL($tableName, $columns, $options); if ($this->supportsCommentOnStatement()) { if ($table->hasOption('comment')) { $sql[] = $this->getCommentOnTableSQL($tableName, $table->getOption('comment')); } foreach ($table->getColumns() as $column) { $comment = $this->getColumnComment($column); if ($comment === null || $comment === '') { continue; } $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getQuotedName($this), $comment); } } return array_merge($sql, $columnSql); } /** * @param list<Table> $tables * * @return list<string> * * @throws Exception */ public function getCreateTablesSQL(array $tables): array { $sql = []; foreach ($tables as $table) { $sql = array_merge($sql, $this->getCreateTableWithoutForeignKeysSQL($table)); } foreach ($tables as $table) { foreach ($table->getForeignKeys() as $foreignKey) { $sql[] = $this->getCreateForeignKeySQL( $foreignKey, $table->getQuotedName($this), ); } } return $sql; } /** * @param list<Table> $tables * * @return list<string> */ public function getDropTablesSQL(array $tables): array { $sql = []; foreach ($tables as $table) { foreach ($table->getForeignKeys() as $foreignKey) { $sql[] = $this->getDropForeignKeySQL( $foreignKey->getQuotedName($this), $table->getQuotedName($this), ); } } foreach ($tables as $table) { $sql[] = $this->getDropTableSQL($table->getQuotedName($this)); } return $sql; } protected function getCommentOnTableSQL(string $tableName, ?string $comment): string { $tableName = new Identifier($tableName); return sprintf( 'COMMENT ON TABLE %s IS %s', $tableName->getQuotedName($this), $this->quoteStringLiteral((string) $comment), ); } /** * @param string $tableName * @param string $columnName * @param string|null $comment * * @return string */ public function getCommentOnColumnSQL($tableName, $columnName, $comment) { $tableName = new Identifier($tableName); $columnName = new Identifier($columnName); return sprintf( 'COMMENT ON COLUMN %s.%s IS %s', $tableName->getQuotedName($this), $columnName->getQuotedName($this), $this->quoteStringLiteral((string) $comment), ); } /** * Returns the SQL to create inline comment on a column. * * @param string $comment * * @return string * * @throws Exception If not supported on this platform. */ public function getInlineColumnCommentSQL($comment) { if (! $this->supportsInlineColumnComments()) { throw Exception::notSupported(__METHOD__); } return 'COMMENT ' . $this->quoteStringLiteral($comment); } /** * Returns the SQL used to create a table. * * @param string $name * @param mixed[][] $columns * @param mixed[] $options * * @return string[] */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $columnListSql = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $index => $definition) { $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); } } if (isset($options['primary']) && ! empty($options['primary'])) { $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')'; } if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index => $definition) { $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition); } } $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; $check = $this->getCheckDeclarationSQL($columns); if (! empty($check)) { $query .= ', ' . $check; } $query .= ')'; $sql = [$query]; if (isset($options['foreignKeys'])) { foreach ($options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } return $sql; } /** @return string */ public function getCreateTemporaryTableSnippetSQL() { return 'CREATE TEMPORARY TABLE'; } /** * Generates SQL statements that can be used to apply the diff. * * @return list<string> */ public function getAlterSchemaSQL(SchemaDiff $diff): array { return $diff->toSql($this); } /** * Returns the SQL to create a sequence on this platform. * * @return string * * @throws Exception If not supported on this platform. */ public function getCreateSequenceSQL(Sequence $sequence) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to change a sequence on this platform. * * @return string * * @throws Exception If not supported on this platform. */ public function getAlterSequenceSQL(Sequence $sequence) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL snippet to drop an existing sequence. * * @param Sequence|string $sequence * * @return string * * @throws Exception If not supported on this platform. */ public function getDropSequenceSQL($sequence) { if (! $this->supportsSequences()) { throw Exception::notSupported(__METHOD__); } if ($sequence instanceof Sequence) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $sequence as a Sequence object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $sequence = $sequence->getQuotedName($this); } return 'DROP SEQUENCE ' . $sequence; } /** * Returns the SQL to create a constraint on a table on this platform. * * @deprecated Use {@see getCreateIndexSQL()}, {@see getCreateForeignKeySQL()} * or {@see getCreateUniqueConstraintSQL()} instead. * * @param Table|string $table * * @return string * * @throws InvalidArgumentException */ public function getCreateConstraintSQL(Constraint $constraint, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this); $columnList = '(' . implode(', ', $constraint->getQuotedColumns($this)) . ')'; $referencesClause = ''; if ($constraint instanceof Index) { if ($constraint->isPrimary()) { $query .= ' PRIMARY KEY'; } elseif ($constraint->isUnique()) { $query .= ' UNIQUE'; } else { throw new InvalidArgumentException( 'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().', ); } } elseif ($constraint instanceof UniqueConstraint) { $query .= ' UNIQUE'; } elseif ($constraint instanceof ForeignKeyConstraint) { $query .= ' FOREIGN KEY'; $referencesClause = ' REFERENCES ' . $constraint->getQuotedForeignTableName($this) . ' (' . implode(', ', $constraint->getQuotedForeignColumns($this)) . ')'; } $query .= ' ' . $columnList . $referencesClause; return $query; } /** * Returns the SQL to create an index on a table on this platform. * * @param Table|string $table The name of the table on which the index is to be created. * * @return string * * @throws InvalidArgumentException */ public function getCreateIndexSQL(Index $index, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } $name = $index->getQuotedName($this); $columns = $index->getColumns(); if (count($columns) === 0) { throw new InvalidArgumentException(sprintf( 'Incomplete or invalid index definition %s on table %s', $name, $table, )); } if ($index->isPrimary()) { return $this->getCreatePrimaryKeySQL($index, $table); } $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; $query .= ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); return $query; } /** * Adds condition for partial index. * * @return string */ protected function getPartialIndexSQL(Index $index) { if ($this->supportsPartialIndexes() && $index->hasOption('where')) { return ' WHERE ' . $index->getOption('where'); } return ''; } /** * Adds additional flags for index generation. * * @return string */ protected function getCreateIndexSQLFlags(Index $index) { return $index->isUnique() ? 'UNIQUE ' : ''; } /** * Returns the SQL to create an unnamed primary key constraint. * * @param Table|string $table * * @return string */ public function getCreatePrimaryKeySQL(Index $index, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; } /** * Returns the SQL to create a named schema. * * @param string $schemaName * * @return string * * @throws Exception If not supported on this platform. */ public function getCreateSchemaSQL($schemaName) { if (! $this->supportsSchemas()) { throw Exception::notSupported(__METHOD__); } return 'CREATE SCHEMA ' . $schemaName; } /** * Returns the SQL to create a unique constraint on a table on this platform. */ public function getCreateUniqueConstraintSQL(UniqueConstraint $constraint, string $tableName): string { return $this->getCreateConstraintSQL($constraint, $tableName); } /** * Returns the SQL snippet to drop a schema. * * @throws Exception If not supported on this platform. */ public function getDropSchemaSQL(string $schemaName): string { if (! $this->supportsSchemas()) { throw Exception::notSupported(__METHOD__); } return 'DROP SCHEMA ' . $schemaName; } /** * Quotes a string so that it can be safely used as a table or column name, * even if it is a reserved word of the platform. This also detects identifier * chains separated by dot and quotes them independently. * * NOTE: Just because you CAN use quoted identifiers doesn't mean * you SHOULD use them. In general, they end up causing way more * problems than they solve. * * @param string $str The identifier name to be quoted. * * @return string The quoted identifier string. */ public function quoteIdentifier($str) { if (strpos($str, '.') !== false) { $parts = array_map([$this, 'quoteSingleIdentifier'], explode('.', $str)); return implode('.', $parts); } return $this->quoteSingleIdentifier($str); } /** * Quotes a single identifier (no dot chain separation). * * @param string $str The identifier name to be quoted. * * @return string The quoted identifier string. */ public function quoteSingleIdentifier($str) { $c = $this->getIdentifierQuoteCharacter(); return $c . str_replace($c, $c . $c, $str) . $c; } /** * Returns the SQL to create a new foreign key. * * @param ForeignKeyConstraint $foreignKey The foreign key constraint. * @param Table|string $table The name of the table on which the foreign key is to be created. * * @return string */ public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } return 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey); } /** * Gets the SQL statements for altering an existing table. * * This method returns an array of SQL statements, since some platforms need several statements. * * @return list<string> * * @throws Exception If not supported on this platform. */ public function getAlterTableSQL(TableDiff $diff) { throw Exception::notSupported(__METHOD__); } /** @return list<string> */ public function getRenameTableSQL(string $oldName, string $newName): array { return [ sprintf('ALTER TABLE %s RENAME TO %s', $oldName, $newName), ]; } /** * @param mixed[] $columnSql * * @return bool */ protected function onSchemaAlterTableAddColumn(Column $column, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableAddColumn)) { return false; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaAlterTableAddColumn, ); $eventArgs = new SchemaAlterTableAddColumnEventArgs($column, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableAddColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string[] $columnSql * * @return bool */ protected function onSchemaAlterTableRemoveColumn(Column $column, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRemoveColumn)) { return false; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaAlterTableRemoveColumn, ); $eventArgs = new SchemaAlterTableRemoveColumnEventArgs($column, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRemoveColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string[] $columnSql * * @return bool */ protected function onSchemaAlterTableChangeColumn(ColumnDiff $columnDiff, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) { return false; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaAlterTableChangeColumn, ); $eventArgs = new SchemaAlterTableChangeColumnEventArgs($columnDiff, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableChangeColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string $oldColumnName * @param string[] $columnSql * * @return bool */ protected function onSchemaAlterTableRenameColumn($oldColumnName, Column $column, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRenameColumn)) { return false; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaAlterTableRenameColumn, ); $eventArgs = new SchemaAlterTableRenameColumnEventArgs($oldColumnName, $column, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRenameColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string[] $sql * * @return bool */ protected function onSchemaAlterTable(TableDiff $diff, &$sql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTable)) { return false; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/5784', 'Subscribing to %s events is deprecated.', Events::onSchemaAlterTable, ); $eventArgs = new SchemaAlterTableEventArgs($diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTable, $eventArgs); $sql = array_merge($sql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** @return string[] */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); $sql = []; if ($this->supportsForeignKeyConstraints()) { foreach ($diff->getDroppedForeignKeys() as $foreignKey) { if ($foreignKey instanceof ForeignKeyConstraint) { $foreignKey = $foreignKey->getQuotedName($this); } $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableNameSQL); } foreach ($diff->getModifiedForeignKeys() as $foreignKey) { $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL); } } foreach ($diff->getDroppedIndexes() as $index) { $sql[] = $this->getDropIndexSQL($index->getQuotedName($this), $tableNameSQL); } foreach ($diff->getModifiedIndexes() as $index) { $sql[] = $this->getDropIndexSQL($index->getQuotedName($this), $tableNameSQL); } return $sql; } /** @return string[] */ protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) { $sql = []; $newName = $diff->getNewName(); if ($newName !== false) { $tableNameSQL = $newName->getQuotedName($this); } else { $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); } if ($this->supportsForeignKeyConstraints()) { foreach ($diff->getAddedForeignKeys() as $foreignKey) { $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL); } foreach ($diff->getModifiedForeignKeys() as $foreignKey) { $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL); } } foreach ($diff->getAddedIndexes() as $index) { $sql[] = $this->getCreateIndexSQL($index, $tableNameSQL); } foreach ($diff->getModifiedIndexes() as $index) { $sql[] = $this->getCreateIndexSQL($index, $tableNameSQL); } foreach ($diff->getRenamedIndexes() as $oldIndexName => $index) { $oldIndexName = new Identifier($oldIndexName); $sql = array_merge( $sql, $this->getRenameIndexSQL($oldIndexName->getQuotedName($this), $index, $tableNameSQL), ); } return $sql; } /** * Returns the SQL for renaming an index on a table. * * @param string $oldIndexName The name of the index to rename from. * @param Index $index The definition of the index to rename to. * @param string $tableName The table to rename the given index on. * * @return string[] The sequence of SQL statements for renaming the given index. */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { return [ $this->getDropIndexSQL($oldIndexName, $tableName), $this->getCreateIndexSQL($index, $tableName), ]; } /** * Gets declaration of a number of columns in bulk. * * @param mixed[][] $columns A multidimensional associative array. * The first dimension determines the column name, while the second * dimension is keyed with the name of the properties * of the column being declared as array indexes. Currently, the types * of supported column properties are as follows: * * length * Integer value that determines the maximum length of the text * column. If this argument is missing the column should be * declared to have the longest length allowed by the DBMS. * * default * Text value to be used as default for this column. * * notnull * Boolean flag that indicates whether this column is constrained * to not be set to null. * charset * Text value with the default CHARACTER SET for this column. * collation * Text value with the default COLLATION for this column. * unique * unique constraint * * @return string */ public function getColumnDeclarationListSQL(array $columns) { $declarations = []; foreach ($columns as $name => $column) { $declarations[] = $this->getColumnDeclarationSQL($name, $column); } return implode(', ', $declarations); } /** * Obtains DBMS specific SQL code portion needed to declare a generic type * column to be used in statements like CREATE TABLE. * * @param string $name The name the column to be declared. * @param mixed[] $column An associative array with the name of the properties * of the column being declared as array indexes. Currently, the types * of supported column properties are as follows: * * length * Integer value that determines the maximum length of the text * column. If this argument is missing the column should be * declared to have the longest length allowed by the DBMS. * * default * Text value to be used as default for this column. * * notnull * Boolean flag that indicates whether this column is constrained * to not be set to null. * charset * Text value with the default CHARACTER SET for this column. * collation * Text value with the default COLLATION for this column. * unique * unique constraint * check * column check constraint * columnDefinition * a string that defines the complete column * * @return string DBMS specific SQL code portion that should be used to declare the column. * * @throws Exception */ public function getColumnDeclarationSQL($name, array $column) { if (isset($column['columnDefinition'])) { $declaration = $this->getCustomTypeDeclarationSQL($column); } else { $default = $this->getDefaultValueDeclarationSQL($column); $charset = ! empty($column['charset']) ? ' ' . $this->getColumnCharsetDeclarationSQL($column['charset']) : ''; $collation = ! empty($column['collation']) ? ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : ''; $notnull = ! empty($column['notnull']) ? ' NOT NULL' : ''; if (! empty($column['unique'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "unique" column property is deprecated. Use unique constraints instead.', ); $unique = ' ' . $this->getUniqueFieldDeclarationSQL(); } else { $unique = ''; } if (! empty($column['check'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "check" column property is deprecated.', ); $check = ' ' . $column['check']; } else { $check = ''; } $typeDecl = $column['type']->getSQLDeclaration($column, $this); $declaration = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation; if ($this->supportsInlineColumnComments() && isset($column['comment']) && $column['comment'] !== '') { $declaration .= ' ' . $this->getInlineColumnCommentSQL($column['comment']); } } return $name . ' ' . $declaration; } /** * Returns the SQL snippet that declares a floating point column of arbitrary precision. * * @param mixed[] $column * * @return string */ public function getDecimalTypeDeclarationSQL(array $column) { if (empty($column['precision'])) { if (! isset($column['precision'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5637', 'Relying on the default decimal column precision is deprecated' . ', specify the precision explicitly.', ); } $precision = 10; } else { $precision = $column['precision']; } if (empty($column['scale'])) { if (! isset($column['scale'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5637', 'Relying on the default decimal column scale is deprecated' . ', specify the scale explicitly.', ); } $scale = 0; } else { $scale = $column['scale']; } return 'NUMERIC(' . $precision . ', ' . $scale . ')'; } /** * Obtains DBMS specific SQL code portion needed to set a default value * declaration to be used in statements like CREATE TABLE. * * @param mixed[] $column The column definition array. * * @return string DBMS specific SQL code portion needed to set a default value. */ public function getDefaultValueDeclarationSQL($column) { if (! isset($column['default'])) { return empty($column['notnull']) ? ' DEFAULT NULL' : ''; } $default = $column['default']; if (! isset($column['type'])) { return " DEFAULT '" . $default . "'"; } $type = $column['type']; if ($type instanceof Types\PhpIntegerMappingType) { return ' DEFAULT ' . $default; } if ($type instanceof Types\PhpDateTimeMappingType && $default === $this->getCurrentTimestampSQL()) { return ' DEFAULT ' . $this->getCurrentTimestampSQL(); } if ($type instanceof Types\TimeType && $default === $this->getCurrentTimeSQL()) { return ' DEFAULT ' . $this->getCurrentTimeSQL(); } if ($type instanceof Types\DateType && $default === $this->getCurrentDateSQL()) { return ' DEFAULT ' . $this->getCurrentDateSQL(); } if ($type instanceof Types\BooleanType) { return ' DEFAULT ' . $this->convertBooleans($default); } return ' DEFAULT ' . $this->quoteStringLiteral($default); } /** * Obtains DBMS specific SQL code portion needed to set a CHECK constraint * declaration to be used in statements like CREATE TABLE. * * @param string[]|mixed[][] $definition The check definition. * * @return string DBMS specific SQL code portion needed to set a CHECK constraint. */ public function getCheckDeclarationSQL(array $definition) { $constraints = []; foreach ($definition as $column => $def) { if (is_string($def)) { $constraints[] = 'CHECK (' . $def . ')'; } else { if (isset($def['min'])) { $constraints[] = 'CHECK (' . $column . ' >= ' . $def['min'] . ')'; } if (isset($def['max'])) { $constraints[] = 'CHECK (' . $column . ' <= ' . $def['max'] . ')'; } } } return implode(', ', $constraints); } /** * Obtains DBMS specific SQL code portion needed to set a unique * constraint declaration to be used in statements like CREATE TABLE. * * @param string $name The name of the unique constraint. * @param UniqueConstraint $constraint The unique constraint definition. * * @return string DBMS specific SQL code portion needed to set a constraint. * * @throws InvalidArgumentException */ public function getUniqueConstraintDeclarationSQL($name, UniqueConstraint $constraint) { $columns = $constraint->getQuotedColumns($this); $name = new Identifier($name); if (count($columns) === 0) { throw new InvalidArgumentException("Incomplete definition. 'columns' required."); } $constraintFlags = array_merge(['UNIQUE'], array_map('strtoupper', $constraint->getFlags())); $constraintName = $name->getQuotedName($this); $columnListNames = $this->getColumnsFieldDeclarationListSQL($columns); return sprintf('CONSTRAINT %s %s (%s)', $constraintName, implode(' ', $constraintFlags), $columnListNames); } /** * Obtains DBMS specific SQL code portion needed to set an index * declaration to be used in statements like CREATE TABLE. * * @param string $name The name of the index. * @param Index $index The index definition. * * @return string DBMS specific SQL code portion needed to set an index. * * @throws InvalidArgumentException */ public function getIndexDeclarationSQL($name, Index $index) { $columns = $index->getColumns(); $name = new Identifier($name); if (count($columns) === 0) { throw new InvalidArgumentException("Incomplete definition. 'columns' required."); } return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name->getQuotedName($this) . ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); } /** * Obtains SQL code portion needed to create a custom column, * e.g. when a column has the "columnDefinition" keyword. * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate. * * @deprecated * * @param mixed[] $column * * @return string */ public function getCustomTypeDeclarationSQL(array $column) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5527', '%s is deprecated.', __METHOD__, ); return $column['columnDefinition']; } /** * Obtains DBMS specific SQL code portion needed to set an index * declaration to be used in statements like CREATE TABLE. * * @deprecated */ public function getIndexFieldDeclarationListSQL(Index $index): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5527', '%s is deprecated.', __METHOD__, ); return implode(', ', $index->getQuotedColumns($this)); } /** * Obtains DBMS specific SQL code portion needed to set an index * declaration to be used in statements like CREATE TABLE. * * @deprecated * * @param mixed[] $columns */ public function getColumnsFieldDeclarationListSQL(array $columns): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5527', '%s is deprecated.', __METHOD__, ); $ret = []; foreach ($columns as $column => $definition) { if (is_array($definition)) { $ret[] = $column; } else { $ret[] = $definition; } } return implode(', ', $ret); } /** * Returns the required SQL string that fits between CREATE ... TABLE * to create the table as a temporary table. * * Should be overridden in driver classes to return the correct string for the * specific database type. * * The default is to return the string "TEMPORARY" - this will result in a * SQL error for any database that does not support temporary tables, or that * requires a different SQL command from "CREATE TEMPORARY TABLE". * * @deprecated * * @return string The string required to be placed between "CREATE" and "TABLE" * to generate a temporary table, if possible. */ public function getTemporaryTableSQL() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getTemporaryTableSQL() is deprecated.', ); return 'TEMPORARY'; } /** * Some vendors require temporary table names to be qualified specially. * * @param string $tableName * * @return string */ public function getTemporaryTableName($tableName) { return $tableName; } /** * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a column declaration to be used in statements like CREATE TABLE. * * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a column declaration. */ public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) { $sql = $this->getForeignKeyBaseDeclarationSQL($foreignKey); $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey); return $sql; } /** * Returns the FOREIGN KEY query section dealing with non-standard options * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... * * @param ForeignKeyConstraint $foreignKey The foreign key definition. * * @return string */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = ''; if ($foreignKey->hasOption('onUpdate')) { $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate')); } if ($foreignKey->hasOption('onDelete')) { $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); } return $query; } /** * Returns the given referential action in uppercase if valid, otherwise throws an exception. * * @param string $action The foreign key referential action. * * @return string * * @throws InvalidArgumentException If unknown referential action given. */ public function getForeignKeyReferentialActionSQL($action) { $upper = strtoupper($action); switch ($upper) { case 'CASCADE': case 'SET NULL': case 'NO ACTION': case 'RESTRICT': case 'SET DEFAULT': return $upper; default: throw new InvalidArgumentException('Invalid foreign key action: ' . $upper); } } /** * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a column declaration to be used in statements like CREATE TABLE. * * @return string * * @throws InvalidArgumentException */ public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) { $sql = ''; if (strlen($foreignKey->getName()) > 0) { $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' '; } $sql .= 'FOREIGN KEY ('; if (count($foreignKey->getLocalColumns()) === 0) { throw new InvalidArgumentException("Incomplete definition. 'local' required."); } if (count($foreignKey->getForeignColumns()) === 0) { throw new InvalidArgumentException("Incomplete definition. 'foreign' required."); } if (strlen($foreignKey->getForeignTableName()) === 0) { throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required."); } return $sql . implode(', ', $foreignKey->getQuotedLocalColumns($this)) . ') REFERENCES ' . $foreignKey->getQuotedForeignTableName($this) . ' (' . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')'; } /** * Obtains DBMS specific SQL code portion needed to set the UNIQUE constraint * of a column declaration to be used in statements like CREATE TABLE. * * @deprecated Use UNIQUE in SQL instead. * * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint * of a column declaration. */ public function getUniqueFieldDeclarationSQL() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getUniqueFieldDeclarationSQL() is deprecated. Use UNIQUE in SQL instead.', ); return 'UNIQUE'; } /** * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET * of a column declaration to be used in statements like CREATE TABLE. * * @param string $charset The name of the charset. * * @return string DBMS specific SQL code portion needed to set the CHARACTER SET * of a column declaration. */ public function getColumnCharsetDeclarationSQL($charset) { return ''; } /** * Obtains DBMS specific SQL code portion needed to set the COLLATION * of a column declaration to be used in statements like CREATE TABLE. * * @param string $collation The name of the collation. * * @return string DBMS specific SQL code portion needed to set the COLLATION * of a column declaration. */ public function getColumnCollationDeclarationSQL($collation) { return $this->supportsColumnCollation() ? 'COLLATE ' . $this->quoteSingleIdentifier($collation) : ''; } /** * Whether the platform prefers identity columns (eg. autoincrement) for ID generation. * Subclasses should override this method to return TRUE if they prefer identity columns. * * @deprecated * * @return bool */ public function prefersIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/1519', 'AbstractPlatform::prefersIdentityColumns() is deprecated.', ); return false; } /** * Some platforms need the boolean values to be converted. * * The default conversion in this implementation converts to integers (false => 0, true => 1). * * Note: if the input is not a boolean the original input might be returned. * * There are two contexts when converting booleans: Literals and Prepared Statements. * This method should handle the literal case * * @param mixed $item A boolean or an array of them. * * @return mixed A boolean database value or an array of them. */ public function convertBooleans($item) { if (is_array($item)) { foreach ($item as $k => $value) { if (! is_bool($value)) { continue; } $item[$k] = (int) $value; } } elseif (is_bool($item)) { $item = (int) $item; } return $item; } /** * Some platforms have boolean literals that needs to be correctly converted * * The default conversion tries to convert value into bool "(bool)$item" * * @param T $item * * @return (T is null ? null : bool) * * @template T */ public function convertFromBoolean($item) { return $item === null ? null : (bool) $item; } /** * This method should handle the prepared statements case. When there is no * distinction, it's OK to use the same method. * * Note: if the input is not a boolean the original input might be returned. * * @param mixed $item A boolean or an array of them. * * @return mixed A boolean database value or an array of them. */ public function convertBooleansToDatabaseValue($item) { return $this->convertBooleans($item); } /** * Returns the SQL specific for the platform to get the current date. * * @return string */ public function getCurrentDateSQL() { return 'CURRENT_DATE'; } /** * Returns the SQL specific for the platform to get the current time. * * @return string */ public function getCurrentTimeSQL() { return 'CURRENT_TIME'; } /** * Returns the SQL specific for the platform to get the current timestamp * * @return string */ public function getCurrentTimestampSQL() { return 'CURRENT_TIMESTAMP'; } /** * Returns the SQL for a given transaction isolation level Connection constant. * * @param int $level * * @return string * * @throws InvalidArgumentException */ protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case TransactionIsolationLevel::READ_UNCOMMITTED: return 'READ UNCOMMITTED'; case TransactionIsolationLevel::READ_COMMITTED: return 'READ COMMITTED'; case TransactionIsolationLevel::REPEATABLE_READ: return 'REPEATABLE READ'; case TransactionIsolationLevel::SERIALIZABLE: return 'SERIALIZABLE'; default: throw new InvalidArgumentException('Invalid isolation level:' . $level); } } /** * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. * * @return string * * @throws Exception If not supported on this platform. */ public function getListDatabasesSQL() { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL statement for retrieving the namespaces defined in the database. * * @deprecated Use {@see AbstractSchemaManager::listSchemaNames()} instead. * * @return string * * @throws Exception If not supported on this platform. */ public function getListNamespacesSQL() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'AbstractPlatform::getListNamespacesSQL() is deprecated,' . ' use AbstractSchemaManager::listSchemaNames() instead.', ); throw Exception::notSupported(__METHOD__); } /** * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. * * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListSequencesSQL($database) { throw Exception::notSupported(__METHOD__); } /** * @deprecated * * @param string $table * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableConstraintsSQL($table) { throw Exception::notSupported(__METHOD__); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableColumnsSQL($table, $database = null) { throw Exception::notSupported(__METHOD__); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @return string * * @throws Exception If not supported on this platform. */ public function getListTablesSQL() { throw Exception::notSupported(__METHOD__); } /** * @deprecated * * @return string * * @throws Exception If not supported on this platform. */ public function getListUsersSQL() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::getListUsersSQL() is deprecated.', ); throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to list all views of a database or user. * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. * * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListViewsSQL($database) { throw Exception::notSupported(__METHOD__); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * Returns the list of indexes for the current database. * * The current database parameter is optional but will always be passed * when using the SchemaManager API and is the database the given table is in. * * Attention: Some platforms only support currentDatabase when they * are connected with that database. Cross-database information schema * requests may be impossible. * * @param string $table * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableIndexesSQL($table, $database = null) { throw Exception::notSupported(__METHOD__); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableForeignKeysSQL($table) { throw Exception::notSupported(__METHOD__); } /** * @param string $name * @param string $sql * * @return string */ public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } /** * @param string $name * * @return string */ public function getDropViewSQL($name) { return 'DROP VIEW ' . $name; } /** * @param string $sequence * * @return string * * @throws Exception If not supported on this platform. */ public function getSequenceNextValSQL($sequence) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to create a new database. * * @param string $name The name of the database that should be created. * * @return string * * @throws Exception If not supported on this platform. */ public function getCreateDatabaseSQL($name) { if (! $this->supportsCreateDropDatabase()) { throw Exception::notSupported(__METHOD__); } return 'CREATE DATABASE ' . $name; } /** * Returns the SQL snippet to drop an existing database. * * @param string $name The name of the database that should be dropped. * * @return string */ public function getDropDatabaseSQL($name) { if (! $this->supportsCreateDropDatabase()) { throw Exception::notSupported(__METHOD__); } return 'DROP DATABASE ' . $name; } /** * Returns the SQL to set the transaction isolation level. * * @param int $level * * @return string * * @throws Exception If not supported on this platform. */ public function getSetTransactionIsolationSQL($level) { throw Exception::notSupported(__METHOD__); } /** * Obtains DBMS specific SQL to be used to create datetime columns in * statements like CREATE TABLE. * * @param mixed[] $column * * @return string * * @throws Exception If not supported on this platform. */ public function getDateTimeTypeDeclarationSQL(array $column) { throw Exception::notSupported(__METHOD__); } /** * Obtains DBMS specific SQL to be used to create datetime with timezone offset columns. * * @param mixed[] $column * * @return string */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return $this->getDateTimeTypeDeclarationSQL($column); } /** * Obtains DBMS specific SQL to be used to create date columns in statements * like CREATE TABLE. * * @param mixed[] $column * * @return string * * @throws Exception If not supported on this platform. */ public function getDateTypeDeclarationSQL(array $column) { throw Exception::notSupported(__METHOD__); } /** * Obtains DBMS specific SQL to be used to create time columns in statements * like CREATE TABLE. * * @param mixed[] $column * * @return string * * @throws Exception If not supported on this platform. */ public function getTimeTypeDeclarationSQL(array $column) { throw Exception::notSupported(__METHOD__); } /** * @param mixed[] $column * * @return string */ public function getFloatDeclarationSQL(array $column) { return 'DOUBLE PRECISION'; } /** * Gets the default transaction isolation level of the platform. * * @see TransactionIsolationLevel * * @return TransactionIsolationLevel::* The default isolation level. */ public function getDefaultTransactionIsolationLevel() { return TransactionIsolationLevel::READ_COMMITTED; } /* supports*() methods */ /** * Whether the platform supports sequences. * * @return bool */ public function supportsSequences() { return false; } /** * Whether the platform supports identity columns. * * Identity columns are columns that receive an auto-generated value from the * database on insert of a row. * * @return bool */ public function supportsIdentityColumns() { return false; } /** * Whether the platform emulates identity columns through sequences. * * Some platforms that do not support identity columns natively * but support sequences can emulate identity columns by using * sequences. * * @deprecated * * @return bool */ public function usesSequenceEmulatedIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return false; } /** * Returns the name of the sequence for a particular identity column in a particular table. * * @deprecated * * @see usesSequenceEmulatedIdentityColumns * * @param string $tableName The name of the table to return the sequence name for. * @param string $columnName The name of the identity column in the table to return the sequence name for. * * @return string * * @throws Exception If not supported on this platform. */ public function getIdentitySequenceName($tableName, $columnName) { throw Exception::notSupported(__METHOD__); } /** * Whether the platform supports indexes. * * @deprecated * * @return bool */ public function supportsIndexes() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsIndexes() is deprecated.', ); return true; } /** * Whether the platform supports partial indexes. * * @return bool */ public function supportsPartialIndexes() { return false; } /** * Whether the platform supports indexes with column length definitions. */ public function supportsColumnLengthIndexes(): bool { return false; } /** * Whether the platform supports altering tables. * * @deprecated All platforms must implement altering tables. * * @return bool */ public function supportsAlterTable() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsAlterTable() is deprecated. All platforms must implement altering tables.', ); return true; } /** * Whether the platform supports transactions. * * @deprecated * * @return bool */ public function supportsTransactions() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsTransactions() is deprecated.', ); return true; } /** * Whether the platform supports savepoints. * * @return bool */ public function supportsSavepoints() { return true; } /** * Whether the platform supports releasing savepoints. * * @return bool */ public function supportsReleaseSavepoints() { return $this->supportsSavepoints(); } /** * Whether the platform supports primary key constraints. * * @deprecated * * @return bool */ public function supportsPrimaryConstraints() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsPrimaryConstraints() is deprecated.', ); return true; } /** * Whether the platform supports foreign key constraints. * * @deprecated All platforms should support foreign key constraints. * * @return bool */ public function supportsForeignKeyConstraints() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5409', 'AbstractPlatform::supportsForeignKeyConstraints() is deprecated.', ); return true; } /** * Whether the platform supports database schemas. * * @return bool */ public function supportsSchemas() { return false; } /** * Whether this platform can emulate schemas. * * @deprecated * * Platforms that either support or emulate schemas don't automatically * filter a schema for the namespaced elements in {@see AbstractManager::introspectSchema()}. * * @return bool */ public function canEmulateSchemas() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4805', 'AbstractPlatform::canEmulateSchemas() is deprecated.', ); return false; } /** * Returns the default schema name. * * @deprecated * * @return string * * @throws Exception If not supported on this platform. */ public function getDefaultSchemaName() { throw Exception::notSupported(__METHOD__); } /** * Whether this platform supports create database. * * Some databases don't allow to create and drop databases at all or only with certain tools. * * @deprecated * * @return bool */ public function supportsCreateDropDatabase() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return true; } /** * Whether the platform supports getting the affected rows of a recent update/delete type query. * * @deprecated * * @return bool */ public function supportsGettingAffectedRows() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsGettingAffectedRows() is deprecated.', ); return true; } /** * Whether this platform support to add inline column comments as postfix. * * @return bool */ public function supportsInlineColumnComments() { return false; } /** * Whether this platform support the proprietary syntax "COMMENT ON asset". * * @return bool */ public function supportsCommentOnStatement() { return false; } /** * Does this platform have native guid type. * * @deprecated * * @return bool */ public function hasNativeGuidType() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return false; } /** * Does this platform have native JSON type. * * @deprecated * * @return bool */ public function hasNativeJsonType() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return false; } /** * Whether this platform supports views. * * @deprecated All platforms must implement support for views. * * @return bool */ public function supportsViews() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsViews() is deprecated. All platforms must implement support for views.', ); return true; } /** * Does this platform support column collation? * * @return bool */ public function supportsColumnCollation() { return false; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored datetime value of this platform. * * @return string The format string. */ public function getDateTimeFormatString() { return 'Y-m-d H:i:s'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored datetime with timezone value of this platform. * * @return string The format string. */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:s'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored date value of this platform. * * @return string The format string. */ public function getDateFormatString() { return 'Y-m-d'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored time value of this platform. * * @return string The format string. */ public function getTimeFormatString() { return 'H:i:s'; } /** * Adds an driver-specific LIMIT clause to the query. * * @param string $query * @param int|null $limit * @param int $offset * * @throws Exception */ final public function modifyLimitQuery($query, $limit, $offset = 0): string { if ($offset < 0) { throw new Exception(sprintf( 'Offset must be a positive integer or zero, %d given', $offset, )); } if ($offset > 0 && ! $this->supportsLimitOffset()) { throw new Exception(sprintf( 'Platform %s does not support offset values in limit queries.', $this->getName(), )); } if ($limit !== null) { $limit = (int) $limit; } return $this->doModifyLimitQuery($query, $limit, (int) $offset); } /** * Adds an platform-specific LIMIT clause to the query. * * @param string $query * @param int|null $limit * @param int $offset * * @return string */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit !== null) { $query .= sprintf(' LIMIT %d', $limit); } if ($offset > 0) { $query .= sprintf(' OFFSET %d', $offset); } return $query; } /** * Whether the database platform support offsets in modify limit clauses. * * @deprecated All platforms must implement support for offsets in modify limit clauses. * * @return bool */ public function supportsLimitOffset() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4724', 'AbstractPlatform::supportsViews() is deprecated.' . ' All platforms must implement support for offsets in modify limit clauses.', ); return true; } /** * Maximum length of any given database identifier, like tables or column names. * * @return int */ public function getMaxIdentifierLength() { return 63; } /** * Returns the insert SQL for an empty insert statement. * * @param string $quotedTableName * @param string $quotedIdentifierColumnName * * @return string */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (null)'; } /** * Generates a Truncate Table SQL statement for a given table. * * Cascade is not supported on many platforms but would optionally cascade the truncate by * following the foreign keys. * * @param string $tableName * @param bool $cascade * * @return string */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); } /** * This is for test reasons, many vendors have special requirements for dummy statements. * * @return string */ public function getDummySelectSQL() { $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; return sprintf('SELECT %s', $expression); } /** * Returns the SQL to create a new savepoint. * * @param string $savepoint * * @return string */ public function createSavePoint($savepoint) { return 'SAVEPOINT ' . $savepoint; } /** * Returns the SQL to release a savepoint. * * @param string $savepoint * * @return string */ public function releaseSavePoint($savepoint) { return 'RELEASE SAVEPOINT ' . $savepoint; } /** * Returns the SQL to rollback a savepoint. * * @param string $savepoint * * @return string */ public function rollbackSavePoint($savepoint) { return 'ROLLBACK TO SAVEPOINT ' . $savepoint; } /** * Returns the keyword list instance of this platform. * * @throws Exception If no keyword list is specified. */ final public function getReservedKeywordsList(): KeywordList { // Store the instance so it doesn't need to be generated on every request. return $this->_keywords ??= $this->createReservedKeywordsList(); } /** * Creates an instance of the reserved keyword list of this platform. * * This method will become @abstract in DBAL 4.0.0. * * @throws Exception */ protected function createReservedKeywordsList(): KeywordList { $class = $this->getReservedKeywordsClass(); $keywords = new $class(); if (! $keywords instanceof KeywordList) { throw Exception::notSupported(__METHOD__); } return $keywords; } /** * Returns the class name of the reserved keywords list. * * @deprecated Implement {@see createReservedKeywordsList()} instead. * * @return string * @psalm-return class-string<KeywordList> * * @throws Exception If not supported on this platform. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'AbstractPlatform::getReservedKeywordsClass() is deprecated,' . ' use AbstractPlatform::createReservedKeywordsList() instead.', ); throw Exception::notSupported(__METHOD__); } /** * Quotes a literal string. * This method is NOT meant to fix SQL injections! * It is only meant to escape this platform's string literal * quote character inside the given literal string. * * @param string $str The literal string to be quoted. * * @return string The quoted literal string. */ public function quoteStringLiteral($str) { $c = $this->getStringLiteralQuoteCharacter(); return $c . str_replace($c, $c . $c, $str) . $c; } /** * Gets the character used for string literal quoting. * * @deprecated Use {@see quoteStringLiteral()} to quote string literals instead. * * @return string */ public function getStringLiteralQuoteCharacter() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5388', 'AbstractPlatform::getStringLiteralQuoteCharacter() is deprecated.' . ' Use quoteStringLiteral() instead.', ); return "'"; } /** * Escapes metacharacters in a string intended to be used with a LIKE * operator. * * @param string $inputString a literal, unquoted string * @param string $escapeChar should be reused by the caller in the LIKE * expression. */ final public function escapeStringForLike(string $inputString, string $escapeChar): string { return preg_replace( '~([' . preg_quote($this->getLikeWildcardCharacters() . $escapeChar, '~') . '])~u', addcslashes($escapeChar, '\\') . '$1', $inputString, ); } /** * @return array<string,mixed> An associative array with the name of the properties * of the column being declared as array indexes. */ private function columnToArray(Column $column): array { $name = $column->getQuotedName($this); return array_merge($column->toArray(), [ 'name' => $name, 'version' => $column->hasPlatformOption('version') ? $column->getPlatformOption('version') : false, 'comment' => $this->getColumnComment($column), ]); } /** @internal */ public function createSQLParser(): Parser { return new Parser(false); } protected function getLikeWildcardCharacters(): string { return '%_'; } /** * Compares the definitions of the given columns in the context of this platform. * * @throws Exception */ public function columnsEqual(Column $column1, Column $column2): bool { $column1Array = $this->columnToArray($column1); $column2Array = $this->columnToArray($column2); // ignore explicit columnDefinition since it's not set on the Column generated by the SchemaManager unset($column1Array['columnDefinition']); unset($column2Array['columnDefinition']); if ( $this->getColumnDeclarationSQL('', $column1Array) !== $this->getColumnDeclarationSQL('', $column2Array) ) { return false; } if (! $this->columnDeclarationsMatch($column1, $column2)) { return false; } // If the platform supports inline comments, all comparison is already done above if ($this->supportsInlineColumnComments()) { return true; } if ($column1->getComment() !== $column2->getComment()) { return false; } // If disableTypeComments is true, we do not need to check types, all comparison is already done above if ($this->disableTypeComments) { return true; } return $column1->getType() === $column2->getType(); } /** * Whether the database data type matches that expected for the doctrine type for the given colunms. */ private function columnDeclarationsMatch(Column $column1, Column $column2): bool { return ! ( $column1->hasPlatformOption('declarationMismatch') || $column2->hasPlatformOption('declarationMismatch') ); } /** * Creates the schema manager that can be used to inspect and change the underlying * database schema according to the dialect of the platform. * * @throws Exception * * @abstract */ public function createSchemaManager(Connection $connection): AbstractSchemaManager { throw Exception::notSupported(__METHOD__); } } dbal/src/Platforms/SQLServerPlatform.php 0000644 00000167674 15021222234 0014266 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception\InvalidLockMode; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Platforms\SQLServer\SQL\Builder\SQLServerSelectSQLBuilder; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\SQLServerSchemaManager; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use function array_merge; use function array_unique; use function array_values; use function count; use function crc32; use function dechex; use function explode; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function is_array; use function is_bool; use function is_numeric; use function is_string; use function preg_match; use function preg_match_all; use function sprintf; use function str_ends_with; use function str_replace; use function str_starts_with; use function strpos; use function strtoupper; use function substr; use function substr_count; use const PREG_OFFSET_CAPTURE; /** * Provides the behavior, features and SQL dialect of the Microsoft SQL Server database platform * of the oldest supported version. */ class SQLServerPlatform extends AbstractPlatform { public function createSelectSQLBuilder(): SelectSQLBuilder { return new SQLServerSelectSQLBuilder($this); } /** * {@inheritDoc} */ public function getCurrentDateSQL() { return $this->getConvertExpression('date', 'GETDATE()'); } /** * {@inheritDoc} */ public function getCurrentTimeSQL() { return $this->getConvertExpression('time', 'GETDATE()'); } /** * Returns an expression that converts an expression of one data type to another. * * @param string $dataType The target native data type. Alias data types cannot be used. * @param string $expression The SQL expression to convert. */ private function getConvertExpression($dataType, $expression): string { return sprintf('CONVERT(%s, %s)', $dataType, $expression); } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { $factorClause = ''; if ($operator === '-') { $factorClause = '-1 * '; } return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')'; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return 'DATEDIFF(day, ' . $date2 . ',' . $date1 . ')'; } /** * {@inheritDoc} * * Microsoft SQL Server prefers "autoincrement" identity columns * since sequences can only be emulated with a table. * * @deprecated */ public function prefersIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/1519', 'SQLServerPlatform::prefersIdentityColumns() is deprecated.', ); return true; } /** * {@inheritDoc} * * Microsoft SQL Server supports this through AUTO_INCREMENT columns. */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} */ public function supportsReleaseSavepoints() { return false; } /** * {@inheritDoc} */ public function supportsSchemas() { return true; } /** * {@inheritDoc} * * @deprecated */ public function getDefaultSchemaName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return 'dbo'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsColumnCollation() { return true; } public function supportsSequences(): bool { return true; } public function getAlterSequenceSQL(Sequence $sequence): string { return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize(); } public function getCreateSequenceSQL(Sequence $sequence): string { return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . ' START WITH ' . $sequence->getInitialValue() . ' INCREMENT BY ' . $sequence->getAllocationSize() . ' MINVALUE ' . $sequence->getInitialValue(); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListSequencesSQL($database) { return 'SELECT seq.name, CAST( seq.increment AS VARCHAR(MAX) ) AS increment, -- CAST avoids driver error for sql_variant type CAST( seq.start_value AS VARCHAR(MAX) ) AS start_value -- CAST avoids driver error for sql_variant type FROM sys.sequences AS seq'; } /** * {@inheritDoc} */ public function getSequenceNextValSQL($sequence) { return 'SELECT NEXT VALUE FOR ' . $sequence; } /** * {@inheritDoc} * * @deprecated */ public function hasNativeGuidType() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } /** * {@inheritDoc} */ public function getDropForeignKeySQL($foreignKey, $table) { if (! $foreignKey instanceof ForeignKeyConstraint) { $foreignKey = new Identifier($foreignKey); } if (! $table instanceof Table) { $table = new Identifier($table); } $foreignKey = $foreignKey->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; } /** * {@inheritDoc} */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $index as an Index object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $index = $index->getQuotedName($this); } elseif (! is_string($index)) { throw new InvalidArgumentException( __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.', ); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as an Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } elseif (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.', ); } return 'DROP INDEX ' . $index . ' ON ' . $table; } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $defaultConstraintsSql = []; $commentsSql = []; $tableComment = $options['comment'] ?? null; if ($tableComment !== null) { $commentsSql[] = $this->getCommentOnTableSQL($name, $tableComment); } // @todo does other code breaks because of this? // force primary keys to be not null foreach ($columns as &$column) { if (! empty($column['primary'])) { $column['notnull'] = true; } // Build default constraints SQL statements. if (isset($column['default'])) { $defaultConstraintsSql[] = 'ALTER TABLE ' . $name . ' ADD' . $this->getDefaultConstraintDeclarationSQL($name, $column); } if (empty($column['comment']) && ! is_numeric($column['comment'])) { continue; } $commentsSql[] = $this->getCreateColumnCommentSQL($name, $column['name'], $column['comment']); } $columnListSql = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $constraintName => $definition) { $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition); } } if (isset($options['primary']) && ! empty($options['primary'])) { $flags = ''; if (isset($options['primary_index']) && $options['primary_index']->hasFlag('nonclustered')) { $flags = ' NONCLUSTERED'; } $columnListSql .= ', PRIMARY KEY' . $flags . ' (' . implode(', ', array_unique(array_values($options['primary']))) . ')'; } $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; $check = $this->getCheckDeclarationSQL($columns); if (! empty($check)) { $query .= ', ' . $check; } $query .= ')'; $sql = [$query]; if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index) { $sql[] = $this->getCreateIndexSQL($index, $name); } } if (isset($options['foreignKeys'])) { foreach ($options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } return array_merge($sql, $commentsSql, $defaultConstraintsSql); } /** * {@inheritDoc} */ public function getCreatePrimaryKeySQL(Index $index, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $identifier = $table->getQuotedName($this); } else { $identifier = $table; } $sql = 'ALTER TABLE ' . $identifier . ' ADD PRIMARY KEY'; if ($index->hasFlag('nonclustered')) { $sql .= ' NONCLUSTERED'; } return $sql . ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; } private function unquoteSingleIdentifier(string $possiblyQuotedName): string { return str_starts_with($possiblyQuotedName, '[') && str_ends_with($possiblyQuotedName, ']') ? substr($possiblyQuotedName, 1, -1) : $possiblyQuotedName; } /** * Returns the SQL statement for creating a column comment. * * SQL Server does not support native column comments, * therefore the extended properties functionality is used * as a workaround to store them. * The property name used to store column comments is "MS_Description" * which provides compatibility with SQL Server Management Studio, * as column comments are stored in the same property there when * specifying a column's "Description" attribute. * * @param string $tableName The quoted table name to which the column belongs. * @param string $columnName The quoted column name to create the comment for. * @param string|null $comment The column's comment. * * @return string */ protected function getCreateColumnCommentSQL($tableName, $columnName, $comment) { if (strpos($tableName, '.') !== false) { [$schemaName, $tableName] = explode('.', $tableName); } else { $schemaName = 'dbo'; } return $this->getAddExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), 'TABLE', $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), 'COLUMN', $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), ); } /** * Returns the SQL snippet for declaring a default constraint. * * @internal The method should be only used from within the SQLServerPlatform class hierarchy. * * @param string $table Name of the table to return the default constraint declaration for. * @param mixed[] $column Column definition. * * @return string * * @throws InvalidArgumentException */ public function getDefaultConstraintDeclarationSQL($table, array $column) { if (! isset($column['default'])) { throw new InvalidArgumentException("Incomplete column definition. 'default' required."); } $columnName = new Identifier($column['name']); return ' CONSTRAINT ' . $this->generateDefaultConstraintName($table, $column['name']) . $this->getDefaultValueDeclarationSQL($column) . ' FOR ' . $columnName->getQuotedName($this); } /** * {@inheritDoc} */ public function getCreateIndexSQL(Index $index, $table) { $constraint = parent::getCreateIndexSQL($index, $table); if ($index->isUnique() && ! $index->isPrimary()) { $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index); } return $constraint; } /** * {@inheritDoc} */ protected function getCreateIndexSQLFlags(Index $index) { $type = ''; if ($index->isUnique()) { $type .= 'UNIQUE '; } if ($index->hasFlag('clustered')) { $type .= 'CLUSTERED '; } elseif ($index->hasFlag('nonclustered')) { $type .= 'NONCLUSTERED '; } return $type; } /** * Extend unique key constraint with required filters * * @param string $sql */ private function _appendUniqueConstraintDefinition($sql, Index $index): string { $fields = []; foreach ($index->getQuotedColumns($this) as $field) { $fields[] = $field . ' IS NOT NULL'; } return $sql . ' WHERE ' . implode(' AND ', $fields); } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $queryParts = []; $sql = []; $columnSql = []; $commentsSql = []; $table = $diff->getOldTable() ?? $diff->getName($this); $tableName = $table->getName(); foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columnProperties = $column->toArray(); $addColumnSql = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnProperties); if (isset($columnProperties['default'])) { $addColumnSql .= ' CONSTRAINT ' . $this->generateDefaultConstraintName( $tableName, $column->getQuotedName($this), ) . $this->getDefaultValueDeclarationSQL($columnProperties); } $queryParts[] = $addColumnSql; $comment = $this->getColumnComment($column); if (empty($comment) && ! is_numeric($comment)) { continue; } $commentsSql[] = $this->getCreateColumnCommentSQL( $tableName, $column->getQuotedName($this), $comment, ); } foreach ($diff->getDroppedColumns() as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); } foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $newColumn = $columnDiff->getNewColumn(); $newComment = $this->getColumnComment($newColumn); $hasNewComment = ! empty($newComment) || is_numeric($newComment); $oldColumn = $columnDiff->getOldColumn(); if ($oldColumn instanceof Column) { $oldComment = $this->getColumnComment($oldColumn); $hasOldComment = ! empty($oldComment) || is_numeric($oldComment); if ($hasOldComment && $hasNewComment && $oldComment !== $newComment) { $commentsSql[] = $this->getAlterColumnCommentSQL( $tableName, $newColumn->getQuotedName($this), $newComment, ); } elseif ($hasOldComment && ! $hasNewComment) { $commentsSql[] = $this->getDropColumnCommentSQL( $tableName, $newColumn->getQuotedName($this), ); } elseif (! $hasOldComment && $hasNewComment) { $commentsSql[] = $this->getCreateColumnCommentSQL( $tableName, $newColumn->getQuotedName($this), $newComment, ); } } // Do not add query part if only comment has changed. if ($columnDiff->hasCommentChanged() && count($columnDiff->changedProperties) === 1) { continue; } $requireDropDefaultConstraint = $this->alterColumnRequiresDropDefaultConstraint($columnDiff); if ($requireDropDefaultConstraint) { $oldColumn = $columnDiff->getOldColumn(); if ($oldColumn !== null) { $oldColumnName = $oldColumn->getName(); } else { $oldColumnName = $columnDiff->oldColumnName; } $queryParts[] = $this->getAlterTableDropDefaultConstraintClause($tableName, $oldColumnName); } $columnProperties = $newColumn->toArray(); $queryParts[] = 'ALTER COLUMN ' . $this->getColumnDeclarationSQL($newColumn->getQuotedName($this), $columnProperties); if ( ! isset($columnProperties['default']) || (! $requireDropDefaultConstraint && ! $columnDiff->hasDefaultChanged()) ) { continue; } $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($tableName, $newColumn); } $tableNameSQL = $table->getQuotedName($this); foreach ($diff->getRenamedColumns() as $oldColumnName => $newColumn) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $newColumn, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $sql[] = "sp_rename '" . $tableNameSQL . '.' . $oldColumnName->getQuotedName($this) . "', '" . $newColumn->getQuotedName($this) . "', 'COLUMN'"; // Recreate default constraint with new column name if necessary (for future reference). if ($newColumn->getDefault() === null) { continue; } $queryParts[] = $this->getAlterTableDropDefaultConstraintClause( $tableName, $oldColumnName->getQuotedName($this), ); $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($tableName, $newColumn); } $tableSql = []; if ($this->onSchemaAlterTable($diff, $tableSql)) { return array_merge($tableSql, $columnSql); } foreach ($queryParts as $query) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } $sql = array_merge($sql, $commentsSql); $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $sql = array_merge($sql, $this->getRenameTableSQL($tableName, $newName->getName())); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff), ); return array_merge($sql, $tableSql, $columnSql); } /** * {@inheritDoc} */ public function getRenameTableSQL(string $oldName, string $newName): array { return [ sprintf('sp_rename %s, %s', $this->quoteStringLiteral($oldName), $this->quoteStringLiteral($newName)), /* Rename table's default constraints names * to match the new table name. * This is necessary to ensure that the default * constraints can be referenced in future table * alterations as the table name is encoded in * default constraints' names. */ sprintf( <<<'SQL' DECLARE @sql NVARCHAR(MAX) = N''; SELECT @sql += N'EXEC sp_rename N''' + dc.name + ''', N''' + REPLACE(dc.name, '%s', '%s') + ''', ''OBJECT'';' FROM sys.default_constraints dc JOIN sys.tables tbl ON dc.parent_object_id = tbl.object_id WHERE tbl.name = %s; EXEC sp_executesql @sql SQL, $this->generateIdentifierName($oldName), $this->generateIdentifierName($newName), $this->quoteStringLiteral($newName), ), ]; } /** * Returns the SQL clause for adding a default constraint in an ALTER TABLE statement. * * @param string $tableName The name of the table to generate the clause for. * @param Column $column The column to generate the clause for. */ private function getAlterTableAddDefaultConstraintClause($tableName, Column $column): string { $columnDef = $column->toArray(); $columnDef['name'] = $column->getQuotedName($this); return 'ADD' . $this->getDefaultConstraintDeclarationSQL($tableName, $columnDef); } /** * Returns the SQL clause for dropping an existing default constraint in an ALTER TABLE statement. * * @param string $tableName The name of the table to generate the clause for. * @param string $columnName The name of the column to generate the clause for. */ private function getAlterTableDropDefaultConstraintClause($tableName, $columnName): string { return 'DROP CONSTRAINT ' . $this->generateDefaultConstraintName($tableName, $columnName); } /** * Checks whether a column alteration requires dropping its default constraint first. * * Different to other database vendors SQL Server implements column default values * as constraints and therefore changes in a column's default value as well as changes * in a column's type require dropping the default constraint first before being to * alter the particular column to the new definition. */ private function alterColumnRequiresDropDefaultConstraint(ColumnDiff $columnDiff): bool { $oldColumn = $columnDiff->getOldColumn(); // We can only decide whether to drop an existing default constraint // if we know the original default value. if (! $oldColumn instanceof Column) { return false; } // We only need to drop an existing default constraint if we know the // column was defined with a default value before. if ($oldColumn->getDefault() === null) { return false; } // We need to drop an existing default constraint if the column was // defined with a default value before and it has changed. if ($columnDiff->hasDefaultChanged()) { return true; } // We need to drop an existing default constraint if the column was // defined with a default value before and the native column type has changed. return $columnDiff->hasTypeChanged() || $columnDiff->hasFixedChanged(); } /** * Returns the SQL statement for altering a column comment. * * SQL Server does not support native column comments, * therefore the extended properties functionality is used * as a workaround to store them. * The property name used to store column comments is "MS_Description" * which provides compatibility with SQL Server Management Studio, * as column comments are stored in the same property there when * specifying a column's "Description" attribute. * * @param string $tableName The quoted table name to which the column belongs. * @param string $columnName The quoted column name to alter the comment for. * @param string|null $comment The column's comment. * * @return string */ protected function getAlterColumnCommentSQL($tableName, $columnName, $comment) { if (strpos($tableName, '.') !== false) { [$schemaName, $tableName] = explode('.', $tableName); } else { $schemaName = 'dbo'; } return $this->getUpdateExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), 'TABLE', $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), 'COLUMN', $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), ); } /** * Returns the SQL statement for dropping a column comment. * * SQL Server does not support native column comments, * therefore the extended properties functionality is used * as a workaround to store them. * The property name used to store column comments is "MS_Description" * which provides compatibility with SQL Server Management Studio, * as column comments are stored in the same property there when * specifying a column's "Description" attribute. * * @param string $tableName The quoted table name to which the column belongs. * @param string $columnName The quoted column name to drop the comment for. * * @return string */ protected function getDropColumnCommentSQL($tableName, $columnName) { if (strpos($tableName, '.') !== false) { [$schemaName, $tableName] = explode('.', $tableName); } else { $schemaName = 'dbo'; } return $this->getDropExtendedPropertySQL( 'MS_Description', 'SCHEMA', $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), 'TABLE', $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), 'COLUMN', $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), ); } /** * {@inheritDoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { return [sprintf( "EXEC sp_rename N'%s.%s', N'%s', N'INDEX'", $tableName, $oldIndexName, $index->getQuotedName($this), ), ]; } /** * Returns the SQL statement for adding an extended property to a database object. * * @internal The method should be only used from within the SQLServerPlatform class hierarchy. * * @link http://msdn.microsoft.com/en-us/library/ms180047%28v=sql.90%29.aspx * * @param string $name The name of the property to add. * @param string|null $value The value of the property to add. * @param string|null $level0Type The type of the object at level 0 the property belongs to. * @param string|null $level0Name The name of the object at level 0 the property belongs to. * @param string|null $level1Type The type of the object at level 1 the property belongs to. * @param string|null $level1Name The name of the object at level 1 the property belongs to. * @param string|null $level2Type The type of the object at level 2 the property belongs to. * @param string|null $level2Name The name of the object at level 2 the property belongs to. * * @return string */ public function getAddExtendedPropertySQL( $name, $value = null, $level0Type = null, $level0Name = null, $level1Type = null, $level1Name = null, $level2Type = null, $level2Name = null ) { return 'EXEC sp_addextendedproperty ' . 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' . 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . ($level2Type !== null || $level2Name !== null ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name : '' ); } /** * Returns the SQL statement for dropping an extended property from a database object. * * @internal The method should be only used from within the SQLServerPlatform class hierarchy. * * @link http://technet.microsoft.com/en-gb/library/ms178595%28v=sql.90%29.aspx * * @param string $name The name of the property to drop. * @param string|null $level0Type The type of the object at level 0 the property belongs to. * @param string|null $level0Name The name of the object at level 0 the property belongs to. * @param string|null $level1Type The type of the object at level 1 the property belongs to. * @param string|null $level1Name The name of the object at level 1 the property belongs to. * @param string|null $level2Type The type of the object at level 2 the property belongs to. * @param string|null $level2Name The name of the object at level 2 the property belongs to. * * @return string */ public function getDropExtendedPropertySQL( $name, $level0Type = null, $level0Name = null, $level1Type = null, $level1Name = null, $level2Type = null, $level2Name = null ) { return 'EXEC sp_dropextendedproperty ' . 'N' . $this->quoteStringLiteral($name) . ', ' . 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . ($level2Type !== null || $level2Name !== null ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name : '' ); } /** * Returns the SQL statement for updating an extended property of a database object. * * @internal The method should be only used from within the SQLServerPlatform class hierarchy. * * @link http://msdn.microsoft.com/en-us/library/ms186885%28v=sql.90%29.aspx * * @param string $name The name of the property to update. * @param string|null $value The value of the property to update. * @param string|null $level0Type The type of the object at level 0 the property belongs to. * @param string|null $level0Name The name of the object at level 0 the property belongs to. * @param string|null $level1Type The type of the object at level 1 the property belongs to. * @param string|null $level1Name The name of the object at level 1 the property belongs to. * @param string|null $level2Type The type of the object at level 2 the property belongs to. * @param string|null $level2Name The name of the object at level 2 the property belongs to. * * @return string */ public function getUpdateExtendedPropertySQL( $name, $value = null, $level0Type = null, $level0Name = null, $level1Type = null, $level1Name = null, $level2Type = null, $level2Name = null ) { return 'EXEC sp_updateextendedproperty ' . 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' . 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . ($level2Type !== null || $level2Name !== null ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name : '' ); } /** * {@inheritDoc} */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { // "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams // Category 2 must be ignored as it is "MS SQL Server 'pseudo-system' object[s]" for replication return 'SELECT name, SCHEMA_NAME (uid) AS schema_name FROM sysobjects' . " WHERE type = 'U' AND name != 'sysdiagrams' AND category != 2 ORDER BY name"; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { return "SELECT col.name, type.name AS type, col.max_length AS length, ~col.is_nullable AS notnull, def.definition AS [default], col.scale, col.precision, col.is_identity AS autoincrement, col.collation_name AS collation, CAST(prop.value AS NVARCHAR(MAX)) AS comment -- CAST avoids driver error for sql_variant type FROM sys.columns AS col JOIN sys.types AS type ON col.user_type_id = type.user_type_id JOIN sys.objects AS obj ON col.object_id = obj.object_id JOIN sys.schemas AS scm ON obj.schema_id = scm.schema_id LEFT JOIN sys.default_constraints def ON col.default_object_id = def.object_id AND col.object_id = def.parent_object_id LEFT JOIN sys.extended_properties AS prop ON obj.object_id = prop.major_id AND col.column_id = prop.minor_id AND prop.name = 'MS_Description' WHERE obj.type = 'U' AND " . $this->getTableWhereClause($table, 'scm.name', 'obj.name'); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { return 'SELECT f.name AS ForeignKey, SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, OBJECT_NAME (f.parent_object_id) AS TableName, COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, f.delete_referential_action_desc, f.update_referential_action_desc FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id ON f.OBJECT_ID = fc.constraint_object_id WHERE ' . $this->getTableWhereClause($table, 'SCHEMA_NAME (f.schema_id)', 'OBJECT_NAME (f.parent_object_id)') . ' ORDER BY fc.constraint_column_id'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) { return "SELECT idx.name AS key_name, col.name AS column_name, ~idx.is_unique AS non_unique, idx.is_primary_key AS [primary], CASE idx.type WHEN '1' THEN 'clustered' WHEN '2' THEN 'nonclustered' ELSE NULL END AS flags FROM sys.tables AS tbl JOIN sys.schemas AS scm ON tbl.schema_id = scm.schema_id JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id WHERE " . $this->getTableWhereClause($table, 'scm.name', 'tbl.name') . ' ORDER BY idx.index_id ASC, idxcol.key_ordinal ASC'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return "SELECT name, definition FROM sysobjects INNER JOIN sys.sql_modules ON sysobjects.id = sys.sql_modules.object_id WHERE type = 'V' ORDER BY name"; } /** * Returns the where clause to filter schema and table name in a query. * * @param string $table The full qualified name of the table. * @param string $schemaColumn The name of the column to compare the schema to in the where clause. * @param string $tableColumn The name of the column to compare the table to in the where clause. */ private function getTableWhereClause($table, $schemaColumn, $tableColumn): string { if (strpos($table, '.') !== false) { [$schema, $table] = explode('.', $table); $schema = $this->quoteStringLiteral($schema); $table = $this->quoteStringLiteral($table); } else { $schema = 'SCHEMA_NAME()'; $table = $this->quoteStringLiteral($table); } return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'CHARINDEX(' . $substr . ', ' . $str . ')'; } return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')'; } /** * {@inheritDoc} */ public function getModExpression($expression1, $expression2) { return $expression1 . ' % ' . $expression2; } /** * {@inheritDoc} */ public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) { if ($char === false) { switch ($mode) { case TrimMode::LEADING: $trimFn = 'LTRIM'; break; case TrimMode::TRAILING: $trimFn = 'RTRIM'; break; default: return 'LTRIM(RTRIM(' . $str . '))'; } return $trimFn . '(' . $str . ')'; } $pattern = "'%[^' + " . $char . " + ']%'"; if ($mode === TrimMode::LEADING) { return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)'; } if ($mode === TrimMode::TRAILING) { return 'reverse(stuff(reverse(' . $str . '), 1, ' . 'patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))'; } return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)), 1, ' . 'patindex(' . $pattern . ', reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null))) - 1, null))'; } /** * {@inheritDoc} */ public function getConcatExpression() { return sprintf('CONCAT(%s)', implode(', ', func_get_args())); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListDatabasesSQL() { return 'SELECT * FROM sys.databases'; } /** * {@inheritDoc} * * @deprecated Use {@see SQLServerSchemaManager::listSchemaNames()} instead. */ public function getListNamespacesSQL() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'SQLServerPlatform::getListNamespacesSQL() is deprecated,' . ' use SQLServerSchemaManager::listSchemaNames() instead.', ); return "SELECT name FROM sys.schemas WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys')"; } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length !== null) { return 'SUBSTRING(' . $string . ', ' . $start . ', ' . $length . ')'; } return 'SUBSTRING(' . $string . ', ' . $start . ', LEN(' . $string . ') - ' . $start . ' + 1)'; } /** * {@inheritDoc} */ public function getLengthExpression($column) { return 'LEN(' . $column . ')'; } public function getCurrentDatabaseExpression(): string { return 'DB_NAME()'; } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getGuidTypeDeclarationSQL(array $column) { return 'UNIQUEIDENTIFIER'; } /** * {@inheritDoc} */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return 'DATETIMEOFFSET(6)'; } /** * {@inheritDoc} */ public function getAsciiStringTypeDeclarationSQL(array $column): string { $length = $column['length'] ?? null; if (empty($column['fixed'])) { return sprintf('VARCHAR(%d)', $length ?? 255); } return sprintf('CHAR(%d)', $length ?? 255); } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default string column length on SQL Server is deprecated' . ', specify the length explicitly.', ); } return $fixed ? 'NCHAR(' . ($length > 0 ? $length : 255) . ')' : 'NVARCHAR(' . ($length > 0 ? $length : 255) . ')'; } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length on SQL Server is deprecated' . ', specify the length explicitly.', ); } return $fixed ? 'BINARY(' . ($length > 0 ? $length : 255) . ')' : 'VARBINARY(' . ($length > 0 ? $length : 255) . ')'; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'SQLServerPlatform::getBinaryMaxLength() is deprecated.', ); return 8000; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'VARCHAR(MAX)'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { return ! empty($column['autoincrement']) ? ' IDENTITY' : ''; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { // 3 - microseconds precision length // http://msdn.microsoft.com/en-us/library/ms187819.aspx return 'DATETIME2(6)'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME(0)'; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'BIT'; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit === null && $offset <= 0) { return $query; } if ($this->shouldAddOrderBy($query)) { if (preg_match('/^SELECT\s+DISTINCT/im', $query) > 0) { // SQL Server won't let us order by a non-selected column in a DISTINCT query, // so we have to do this madness. This says, order by the first column in the // result. SQL Server's docs say that a nonordered query's result order is non- // deterministic anyway, so this won't do anything that a bunch of update and // deletes to the table wouldn't do anyway. $query .= ' ORDER BY 1'; } else { // In another DBMS, we could do ORDER BY 0, but SQL Server gets angry if you // use constant expressions in the order by list. $query .= ' ORDER BY (SELECT 0)'; } } // This looks somewhat like MYSQL, but limit/offset are in inverse positions // Supposedly SQL:2008 core standard. // Per TSQL spec, FETCH NEXT n ROWS ONLY is not valid without OFFSET n ROWS. $query .= sprintf(' OFFSET %d ROWS', $offset); if ($limit !== null) { $query .= sprintf(' FETCH NEXT %d ROWS ONLY', $limit); } return $query; } /** * {@inheritDoc} */ public function convertBooleans($item) { if (is_array($item)) { foreach ($item as $key => $value) { if (! is_bool($value) && ! is_numeric($value)) { continue; } $item[$key] = (int) (bool) $value; } } elseif (is_bool($item) || is_numeric($item)) { $item = (int) (bool) $item; } return $item; } /** * {@inheritDoc} */ public function getCreateTemporaryTableSnippetSQL() { return 'CREATE TABLE'; } /** * {@inheritDoc} */ public function getTemporaryTableName($tableName) { return '#' . $tableName; } /** * {@inheritDoc} */ public function getDateTimeFormatString() { return 'Y-m-d H:i:s.u'; } /** * {@inheritDoc} */ public function getDateFormatString() { return 'Y-m-d'; } /** * {@inheritDoc} */ public function getTimeFormatString() { return 'H:i:s'; } /** * {@inheritDoc} */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:s.u P'; } /** * {@inheritDoc} */ public function getName() { return 'mssql'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'bigint' => Types::BIGINT, 'binary' => Types::BINARY, 'bit' => Types::BOOLEAN, 'blob' => Types::BLOB, 'char' => Types::STRING, 'date' => Types::DATE_MUTABLE, 'datetime' => Types::DATETIME_MUTABLE, 'datetime2' => Types::DATETIME_MUTABLE, 'datetimeoffset' => Types::DATETIMETZ_MUTABLE, 'decimal' => Types::DECIMAL, 'double' => Types::FLOAT, 'double precision' => Types::FLOAT, 'float' => Types::FLOAT, 'image' => Types::BLOB, 'int' => Types::INTEGER, 'money' => Types::INTEGER, 'nchar' => Types::STRING, 'ntext' => Types::TEXT, 'numeric' => Types::DECIMAL, 'nvarchar' => Types::STRING, 'real' => Types::FLOAT, 'smalldatetime' => Types::DATETIME_MUTABLE, 'smallint' => Types::SMALLINT, 'smallmoney' => Types::INTEGER, 'sysname' => Types::STRING, 'text' => Types::TEXT, 'time' => Types::TIME_MUTABLE, 'tinyint' => Types::SMALLINT, 'uniqueidentifier' => Types::GUID, 'varbinary' => Types::BINARY, 'varchar' => Types::STRING, 'xml' => Types::TEXT, ]; } /** * {@inheritDoc} */ public function createSavePoint($savepoint) { return 'SAVE TRANSACTION ' . $savepoint; } /** * {@inheritDoc} */ public function releaseSavePoint($savepoint) { return ''; } /** * {@inheritDoc} */ public function rollbackSavePoint($savepoint) { return 'ROLLBACK TRANSACTION ' . $savepoint; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getForeignKeyReferentialActionSQL($action) { // RESTRICT is not supported, therefore falling back to NO ACTION. if (strtoupper($action) === 'RESTRICT') { return 'NO ACTION'; } return parent::getForeignKeyReferentialActionSQL($action); } public function appendLockHint(string $fromClause, int $lockMode): string { switch ($lockMode) { case LockMode::NONE: case LockMode::OPTIMISTIC: return $fromClause; case LockMode::PESSIMISTIC_READ: return $fromClause . ' WITH (HOLDLOCK, ROWLOCK)'; case LockMode::PESSIMISTIC_WRITE: return $fromClause . ' WITH (UPDLOCK, ROWLOCK)'; default: throw InvalidLockMode::fromLockMode($lockMode); } } /** * {@inheritDoc} * * @deprecated This API is not portable. */ public function getForUpdateSQL() { return ' '; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'SQLServerPlatform::getReservedKeywordsClass() is deprecated,' . ' use SQLServerPlatform::createReservedKeywordsList() instead.', ); return Keywords\SQLServer2012Keywords::class; } /** * {@inheritDoc} */ public function quoteSingleIdentifier($str) { return '[' . str_replace(']', ']]', $str) . ']'; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'VARBINARY(MAX)'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getColumnDeclarationSQL($name, array $column) { if (isset($column['columnDefinition'])) { $columnDef = $this->getCustomTypeDeclarationSQL($column); } else { $collation = ! empty($column['collation']) ? ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : ''; $notnull = ! empty($column['notnull']) ? ' NOT NULL' : ''; if (! empty($column['unique'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "unique" column property is deprecated. Use unique constraints instead.', ); $unique = ' ' . $this->getUniqueFieldDeclarationSQL(); } else { $unique = ''; } if (! empty($column['check'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "check" column property is deprecated.', ); $check = ' ' . $column['check']; } else { $check = ''; } $typeDecl = $column['type']->getSQLDeclaration($column, $this); $columnDef = $typeDecl . $collation . $notnull . $unique . $check; } return $name . ' ' . $columnDef; } /** * {@inheritDoc} * * SQL Server does not support quoting collation identifiers. */ public function getColumnCollationDeclarationSQL($collation) { return 'COLLATE ' . $collation; } public function columnsEqual(Column $column1, Column $column2): bool { if (! parent::columnsEqual($column1, $column2)) { return false; } return $this->getDefaultValueDeclarationSQL($column1->toArray()) === $this->getDefaultValueDeclarationSQL($column2->toArray()); } protected function getLikeWildcardCharacters(): string { return parent::getLikeWildcardCharacters() . '[]^'; } /** * Returns a unique default constraint name for a table and column. * * @param string $table Name of the table to generate the unique default constraint name for. * @param string $column Name of the column in the table to generate the unique default constraint name for. */ private function generateDefaultConstraintName($table, $column): string { return 'DF_' . $this->generateIdentifierName($table) . '_' . $this->generateIdentifierName($column); } /** * Returns a hash value for a given identifier. * * @param string $identifier Identifier to generate a hash value for. */ private function generateIdentifierName($identifier): string { // Always generate name for unquoted identifiers to ensure consistency. $identifier = new Identifier($identifier); return strtoupper(dechex(crc32($identifier->getName()))); } protected function getCommentOnTableSQL(string $tableName, ?string $comment): string { return $this->getAddExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', $this->quoteStringLiteral('dbo'), 'TABLE', $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), ); } /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ public function getListTableMetadataSQL(string $table): string { return sprintf( <<<'SQL' SELECT p.value AS [table_comment] FROM sys.tables AS tbl INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1 WHERE (tbl.name=N%s and SCHEMA_NAME(tbl.schema_id)=N'dbo' and p.name=N'MS_Description') SQL , $this->quoteStringLiteral($table), ); } /** @param string $query */ private function shouldAddOrderBy($query): bool { // Find the position of the last instance of ORDER BY and ensure it is not within a parenthetical statement // but can be in a newline $matches = []; $matchesCount = preg_match_all('/[\\s]+order\\s+by\\s/im', $query, $matches, PREG_OFFSET_CAPTURE); if ($matchesCount === 0) { return true; } // ORDER BY instance may be in a subquery after ORDER BY // e.g. SELECT col1 FROM test ORDER BY (SELECT col2 from test ORDER BY col2) // if in the searched query ORDER BY clause was found where // number of open parentheses after the occurrence of the clause is equal to // number of closed brackets after the occurrence of the clause, // it means that ORDER BY is included in the query being checked while ($matchesCount > 0) { $orderByPos = $matches[0][--$matchesCount][1]; $openBracketsCount = substr_count($query, '(', $orderByPos); $closedBracketsCount = substr_count($query, ')', $orderByPos); if ($openBracketsCount === $closedBracketsCount) { return false; } } return true; } public function createSchemaManager(Connection $connection): SQLServerSchemaManager { return new SQLServerSchemaManager($connection, $this); } } dbal/src/Platforms/PostgreSQLPlatform.php 0000644 00000122271 15021222234 0014423 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\PostgreSQLSchemaManager; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\Types\BinaryType; use Doctrine\DBAL\Types\BlobType; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use UnexpectedValueException; use function array_diff; use function array_merge; use function array_unique; use function array_values; use function count; use function explode; use function implode; use function in_array; use function is_array; use function is_bool; use function is_numeric; use function is_string; use function sprintf; use function strpos; use function strtolower; use function trim; /** * Provides the behavior, features and SQL dialect of the PostgreSQL database platform of the oldest supported version. */ class PostgreSQLPlatform extends AbstractPlatform { private bool $useBooleanTrueFalseStrings = true; /** @var string[][] PostgreSQL booleans literals */ private array $booleanLiterals = [ 'true' => [ 't', 'true', 'y', 'yes', 'on', '1', ], 'false' => [ 'f', 'false', 'n', 'no', 'off', '0', ], ]; /** * PostgreSQL has different behavior with some drivers * with regard to how booleans have to be handled. * * Enables use of 'true'/'false' or otherwise 1 and 0 instead. * * @param bool $flag * * @return void */ public function setUseBooleanTrueFalseStrings($flag) { $this->useBooleanTrueFalseStrings = (bool) $flag; } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length === null) { return 'SUBSTRING(' . $string . ' FROM ' . $start . ')'; } return 'SUBSTRING(' . $string . ' FROM ' . $start . ' FOR ' . $length . ')'; } /** * {@inheritDoc} * * @deprecated Generate dates within the application. */ public function getNowExpression() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4753', 'PostgreSQLPlatform::getNowExpression() is deprecated. Generate dates within the application.', ); return 'LOCALTIMESTAMP(0)'; } /** * {@inheritDoc} */ public function getRegexpExpression() { return 'SIMILAR TO'; } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos !== false) { $str = $this->getSubstringExpression($str, $startPos); return 'CASE WHEN (POSITION(' . $substr . ' IN ' . $str . ') = 0) THEN 0' . ' ELSE (POSITION(' . $substr . ' IN ' . $str . ') + ' . $startPos . ' - 1) END'; } return 'POSITION(' . $substr . ' IN ' . $str . ')'; } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { if ($unit === DateIntervalUnit::QUARTER) { $interval = $this->multiplyInterval((string) $interval, 3); $unit = DateIntervalUnit::MONTH; } return '(' . $date . ' ' . $operator . ' (' . $interval . " || ' " . $unit . "')::interval)"; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))'; } public function getCurrentDatabaseExpression(): string { return 'CURRENT_DATABASE()'; } /** * {@inheritDoc} */ public function supportsSequences() { return true; } /** * {@inheritDoc} */ public function supportsSchemas() { return true; } /** * {@inheritDoc} * * @deprecated */ public function getDefaultSchemaName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return 'public'; } /** * {@inheritDoc} */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsPartialIndexes() { return true; } /** * {@inheritDoc} * * @deprecated */ public function usesSequenceEmulatedIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return true; } /** * {@inheritDoc} * * @deprecated */ public function getIdentitySequenceName($tableName, $columnName) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return $tableName . '_' . $columnName . '_seq'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsCommentOnStatement() { return true; } /** * {@inheritDoc} * * @deprecated */ public function hasNativeGuidType() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } public function createSelectSQLBuilder(): SelectSQLBuilder { return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', null); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListDatabasesSQL() { return 'SELECT datname FROM pg_database'; } /** * {@inheritDoc} * * @deprecated Use {@see PostgreSQLSchemaManager::listSchemaNames()} instead. */ public function getListNamespacesSQL() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4503', 'PostgreSQLPlatform::getListNamespacesSQL() is deprecated,' . ' use PostgreSQLSchemaManager::listSchemaNames() instead.', ); return "SELECT schema_name AS nspname FROM information_schema.schemata WHERE schema_name NOT LIKE 'pg\_%' AND schema_name != 'information_schema'"; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListSequencesSQL($database) { return 'SELECT sequence_name AS relname, sequence_schema AS schemaname, minimum_value AS min_value, increment AS increment_by FROM information_schema.sequences WHERE sequence_catalog = ' . $this->quoteStringLiteral($database) . " AND sequence_schema NOT LIKE 'pg\_%' AND sequence_schema != 'information_schema'"; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { return "SELECT quote_ident(table_name) AS table_name, table_schema AS schema_name FROM information_schema.tables WHERE table_schema NOT LIKE 'pg\_%' AND table_schema != 'information_schema' AND table_name != 'geometry_columns' AND table_name != 'spatial_ref_sys' AND table_type != 'VIEW'"; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return 'SELECT quote_ident(table_name) AS viewname, table_schema AS schemaname, view_definition AS definition FROM information_schema.views WHERE view_definition IS NOT NULL'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { return 'SELECT quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r WHERE r.conrelid = ( SELECT c.oid FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n WHERE ' . $this->getTableWhereClause($table) . " AND n.oid = c.relnamespace ) AND r.contype = 'f'"; } /** * @deprecated * * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { $table = new Identifier($table); $table = $this->quoteStringLiteral($table->getName()); return sprintf( <<<'SQL' SELECT quote_ident(relname) as relname FROM pg_class WHERE oid IN ( SELECT indexrelid FROM pg_index, pg_class WHERE pg_class.relname = %s AND pg_class.oid = pg_index.indrelid AND (indisunique = 't' OR indisprimary = 't') ) SQL , $table, ); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ public function getListTableIndexesSQL($table, $database = null) { return 'SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary, pg_index.indkey, pg_index.indrelid, pg_get_expr(indpred, indrelid) AS where FROM pg_class, pg_index WHERE oid IN ( SELECT indexrelid FROM pg_index si, pg_class sc, pg_namespace sn WHERE ' . $this->getTableWhereClause($table, 'sc', 'sn') . ' AND sc.oid=si.indrelid AND sc.relnamespace = sn.oid ) AND pg_index.indexrelid = oid'; } /** * @param string $table * @param string $classAlias * @param string $namespaceAlias */ private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n'): string { $whereClause = $namespaceAlias . ".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND "; if (strpos($table, '.') !== false) { [$schema, $table] = explode('.', $table); $schema = $this->quoteStringLiteral($schema); } else { $schema = 'ANY(current_schemas(false))'; } $table = new Identifier($table); $table = $this->quoteStringLiteral($table->getName()); return $whereClause . sprintf( '%s.relname = %s AND %s.nspname = %s', $classAlias, $table, $namespaceAlias, $schema, ); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { return "SELECT 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, (SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE c.oid = pg_attrdef.adrelid AND pg_attrdef.adnum=a.attnum ) 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, pg_class c, pg_type t, pg_namespace n WHERE " . $this->getTableWhereClause($table, 'c', 'n') . ' AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid AND n.oid = c.relnamespace ORDER BY a.attnum'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = ''; if ($foreignKey->hasOption('match')) { $query .= ' MATCH ' . $foreignKey->getOption('match'); } $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) { $query .= ' DEFERRABLE'; } else { $query .= ' NOT DEFERRABLE'; } if ( ($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false) || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) ) { $query .= ' INITIALLY DEFERRED'; } else { $query .= ' INITIALLY IMMEDIATE'; } return $query; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = []; $commentsSQL = []; $columnSql = []; $table = $diff->getOldTable() ?? $diff->getName($this); $tableNameSQL = $table->getQuotedName($this); foreach ($diff->getAddedColumns() as $addedColumn) { if ($this->onSchemaAlterTableAddColumn($addedColumn, $diff, $columnSql)) { continue; } $query = 'ADD ' . $this->getColumnDeclarationSQL( $addedColumn->getQuotedName($this), $addedColumn->toArray(), ); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; $comment = $this->getColumnComment($addedColumn); if ($comment === null || $comment === '') { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $addedColumn->getQuotedName($this), $comment, ); } foreach ($diff->getDroppedColumns() as $droppedColumn) { if ($this->onSchemaAlterTableRemoveColumn($droppedColumn, $diff, $columnSql)) { continue; } $query = 'DROP ' . $droppedColumn->getQuotedName($this); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } if ($this->isUnchangedBinaryColumn($columnDiff)) { continue; } $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); $newColumn = $columnDiff->getNewColumn(); $oldColumnName = $oldColumn->getQuotedName($this); if ( $columnDiff->hasTypeChanged() || $columnDiff->hasPrecisionChanged() || $columnDiff->hasScaleChanged() || $columnDiff->hasFixedChanged() ) { $type = $newColumn->getType(); // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type $columnDefinition = $newColumn->toArray(); $columnDefinition['autoincrement'] = false; // here was a server version check before, but DBAL API does not support this anymore. $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSQLDeclaration($columnDefinition, $this); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } if ($columnDiff->hasDefaultChanged()) { $defaultClause = $newColumn->getDefault() === null ? ' DROP DEFAULT' : ' SET' . $this->getDefaultValueDeclarationSQL($newColumn->toArray()); $query = 'ALTER ' . $oldColumnName . $defaultClause; $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } if ($columnDiff->hasNotNullChanged()) { $query = 'ALTER ' . $oldColumnName . ' ' . ($newColumn->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL'; $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } if ($columnDiff->hasAutoIncrementChanged()) { if ($newColumn->getAutoincrement()) { // add autoincrement $seqName = $this->getIdentitySequenceName( $table->getName(), $oldColumnName, ); $sql[] = 'CREATE SEQUENCE ' . $seqName; $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ') FROM ' . $tableNameSQL . '))'; $query = 'ALTER ' . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')"; } else { // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have $query = 'ALTER ' . $oldColumnName . ' DROP DEFAULT'; } $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } $oldComment = $this->getOldColumnComment($columnDiff); $newComment = $this->getColumnComment($newColumn); if ( $columnDiff->hasCommentChanged() || ($columnDiff->getOldColumn() !== null && $oldComment !== $newComment) ) { $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $newColumn->getQuotedName($this), $newComment, ); } if (! $columnDiff->hasLengthChanged()) { continue; } $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $newColumn->getType()->getSQLDeclaration($newColumn->toArray(), $this); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); } $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { $sql = array_merge($sql, $commentsSQL); $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $sql[] = sprintf( 'ALTER TABLE %s RENAME TO %s', $tableNameSQL, $newName->getQuotedName($this), ); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff), ); } return array_merge($sql, $tableSql, $columnSql); } /** * Checks whether a given column diff is a logically unchanged binary type column. * * Used to determine whether a column alteration for a binary type column can be skipped. * Doctrine's {@see BinaryType} and {@see BlobType} are mapped to the same database column type on this platform * as this platform does not have a native VARBINARY/BINARY column type. Therefore the comparator * might detect differences for binary type columns which do not have to be propagated * to database as there actually is no difference at database level. */ private function isUnchangedBinaryColumn(ColumnDiff $columnDiff): bool { $newColumnType = $columnDiff->getNewColumn()->getType(); if (! $newColumnType instanceof BinaryType && ! $newColumnType instanceof BlobType) { return false; } $oldColumn = $columnDiff->getOldColumn() instanceof Column ? $columnDiff->getOldColumn() : null; if ($oldColumn !== null) { $oldColumnType = $oldColumn->getType(); if (! $oldColumnType instanceof BinaryType && ! $oldColumnType instanceof BlobType) { return false; } return count(array_diff($columnDiff->changedProperties, ['type', 'length', 'fixed'])) === 0; } if ($columnDiff->hasTypeChanged()) { return false; } return count(array_diff($columnDiff->changedProperties, ['length', 'fixed'])) === 0; } /** * {@inheritDoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { if (strpos($tableName, '.') !== false) { [$schema] = explode('.', $tableName); $oldIndexName = $schema . '.' . $oldIndexName; } return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)]; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getCommentOnColumnSQL($tableName, $columnName, $comment) { $tableName = new Identifier($tableName); $columnName = new Identifier($columnName); $comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment); return sprintf( 'COMMENT ON COLUMN %s.%s IS %s', $tableName->getQuotedName($this), $columnName->getQuotedName($this), $comment, ); } /** * {@inheritDoc} */ public function getCreateSequenceSQL(Sequence $sequence) { return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize() . ' MINVALUE ' . $sequence->getInitialValue() . ' START ' . $sequence->getInitialValue() . $this->getSequenceCacheSQL($sequence); } /** * {@inheritDoc} */ public function getAlterSequenceSQL(Sequence $sequence) { return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize() . $this->getSequenceCacheSQL($sequence); } /** * Cache definition for sequences */ private function getSequenceCacheSQL(Sequence $sequence): string { if ($sequence->getCache() > 1) { return ' CACHE ' . $sequence->getCache(); } return ''; } /** * {@inheritDoc} */ public function getDropSequenceSQL($sequence) { return parent::getDropSequenceSQL($sequence) . ' CASCADE'; } /** * {@inheritDoc} */ public function getDropForeignKeySQL($foreignKey, $table) { return $this->getDropConstraintSQL($foreignKey, $table); } /** * {@inheritDoc} */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index && $index->isPrimary() && $table !== null) { $constraintName = $index->getName() === 'primary' ? $this->tableName($table) . '_pkey' : $index->getName(); return $this->getDropConstraintSQL($constraintName, $table); } if ($index === '"primary"' && $table !== null) { $constraintName = $this->tableName($table) . '_pkey'; return $this->getDropConstraintSQL($constraintName, $table); } return parent::getDropIndexSQL($index, $table); } /** * @param Table|string|null $table * * @return string */ private function tableName($table) { return $table instanceof Table ? $table->getName() : (string) $table; } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['primary']) && ! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } $unlogged = isset($options['unlogged']) && $options['unlogged'] === true ? ' UNLOGGED' : ''; $query = 'CREATE' . $unlogged . ' TABLE ' . $name . ' (' . $queryFields . ')'; $sql = [$query]; if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index) { $sql[] = $this->getCreateIndexSQL($index, $name); } } if (isset($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $uniqueConstraint) { $sql[] = $this->getCreateConstraintSQL($uniqueConstraint, $name); } } if (isset($options['foreignKeys'])) { foreach ($options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } return $sql; } /** * Converts a single boolean value. * * First converts the value to its native PHP boolean type * and passes it to the given callback function to be reconverted * into any custom representation. * * @param mixed $value The value to convert. * @param callable $callback The callback function to use for converting the real boolean value. * * @return mixed * * @throws UnexpectedValueException */ private function convertSingleBooleanValue($value, $callback) { if ($value === null) { return $callback(null); } if (is_bool($value) || is_numeric($value)) { return $callback((bool) $value); } if (! is_string($value)) { return $callback(true); } /** * Better safe than sorry: http://php.net/in_array#106319 */ if (in_array(strtolower(trim($value)), $this->booleanLiterals['false'], true)) { return $callback(false); } if (in_array(strtolower(trim($value)), $this->booleanLiterals['true'], true)) { return $callback(true); } throw new UnexpectedValueException(sprintf("Unrecognized boolean literal '%s'", $value)); } /** * Converts one or multiple boolean values. * * First converts the value(s) to their native PHP boolean type * and passes them to the given callback function to be reconverted * into any custom representation. * * @param mixed $item The value(s) to convert. * @param callable $callback The callback function to use for converting the real boolean value(s). * * @return mixed */ private function doConvertBooleans($item, $callback) { if (is_array($item)) { foreach ($item as $key => $value) { $item[$key] = $this->convertSingleBooleanValue($value, $callback); } return $item; } return $this->convertSingleBooleanValue($item, $callback); } /** * {@inheritDoc} * * Postgres wants boolean values converted to the strings 'true'/'false'. */ public function convertBooleans($item) { if (! $this->useBooleanTrueFalseStrings) { return parent::convertBooleans($item); } return $this->doConvertBooleans( $item, /** @param mixed $value */ static function ($value) { if ($value === null) { return 'NULL'; } return $value === true ? 'true' : 'false'; }, ); } /** * {@inheritDoc} */ public function convertBooleansToDatabaseValue($item) { if (! $this->useBooleanTrueFalseStrings) { return parent::convertBooleansToDatabaseValue($item); } return $this->doConvertBooleans( $item, /** @param mixed $value */ static function ($value): ?int { return $value === null ? null : (int) $value; }, ); } /** * {@inheritDoc} * * @param T $item * * @return (T is null ? null : bool) * * @template T */ public function convertFromBoolean($item) { if ($item !== null && in_array(strtolower($item), $this->booleanLiterals['false'], true)) { return false; } return parent::convertFromBoolean($item); } /** * {@inheritDoc} */ public function getSequenceNextValSQL($sequence) { return "SELECT NEXTVAL('" . $sequence . "')"; } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'BOOLEAN'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { if (! empty($column['autoincrement'])) { return 'SERIAL'; } return 'INT'; } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { if (! empty($column['autoincrement'])) { return 'BIGSERIAL'; } return 'BIGINT'; } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { if (! empty($column['autoincrement'])) { return 'SMALLSERIAL'; } return 'SMALLINT'; } /** * {@inheritDoc} */ public function getGuidTypeDeclarationSQL(array $column) { return 'UUID'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0) WITHOUT TIME ZONE'; } /** * {@inheritDoc} */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0) WITH TIME ZONE'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME(0) WITHOUT TIME ZONE'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { return ''; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return 'BYTEA'; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'TEXT'; } /** * {@inheritDoc} */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4749', 'PostgreSQLPlatform::getName() is deprecated. Identify platforms by their class.', ); return 'postgresql'; } /** * {@inheritDoc} */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:sO'; } /** * {@inheritDoc} */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); $sql = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); if ($cascade) { $sql .= ' CASCADE'; } return $sql; } /** * Get the snippet used to retrieve the default value for a given column */ public function getDefaultColumnValueSQLSnippet(): string { return <<<'SQL' SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE c.oid = pg_attrdef.adrelid AND pg_attrdef.adnum=a.attnum SQL; } /** * {@inheritDoc} */ public function getReadLockSQL() { return 'FOR SHARE'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'bigint' => Types::BIGINT, 'bigserial' => Types::BIGINT, 'bool' => Types::BOOLEAN, 'boolean' => Types::BOOLEAN, 'bpchar' => Types::STRING, 'bytea' => Types::BLOB, 'char' => Types::STRING, 'date' => Types::DATE_MUTABLE, 'datetime' => Types::DATETIME_MUTABLE, 'decimal' => Types::DECIMAL, 'double' => Types::FLOAT, 'double precision' => Types::FLOAT, 'float' => Types::FLOAT, 'float4' => Types::FLOAT, 'float8' => Types::FLOAT, 'inet' => Types::STRING, 'int' => Types::INTEGER, 'int2' => Types::SMALLINT, 'int4' => Types::INTEGER, 'int8' => Types::BIGINT, 'integer' => Types::INTEGER, 'interval' => Types::STRING, 'json' => Types::JSON, 'jsonb' => Types::JSON, 'money' => Types::DECIMAL, 'numeric' => Types::DECIMAL, 'serial' => Types::INTEGER, 'serial4' => Types::INTEGER, 'serial8' => Types::BIGINT, 'real' => Types::FLOAT, 'smallint' => Types::SMALLINT, 'text' => Types::TEXT, 'time' => Types::TIME_MUTABLE, 'timestamp' => Types::DATETIME_MUTABLE, 'timestamptz' => Types::DATETIMETZ_MUTABLE, 'timetz' => Types::TIME_MUTABLE, 'tsvector' => Types::TEXT, 'uuid' => Types::GUID, 'varchar' => Types::STRING, 'year' => Types::DATE_MUTABLE, '_varchar' => Types::STRING, ]; } /** * {@inheritDoc} * * @deprecated */ public function getVarcharMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'PostgreSQLPlatform::getVarcharMaxLength() is deprecated.', ); return 65535; } /** * {@inheritDoc} */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'PostgreSQLPlatform::getBinaryMaxLength() is deprecated.', ); return 0; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryDefaultLength() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length is deprecated, specify the length explicitly.', ); return 0; } /** * {@inheritDoc} * * @deprecated */ public function hasNativeJsonType() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5509', '%s is deprecated.', __METHOD__, ); return true; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'PostgreSQLPlatform::getReservedKeywordsClass() is deprecated,' . ' use PostgreSQLPlatform::createReservedKeywordsList() instead.', ); return Keywords\PostgreSQL94Keywords::class; } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'BYTEA'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getDefaultValueDeclarationSQL($column) { if (isset($column['autoincrement']) && $column['autoincrement'] === true) { return ''; } return parent::getDefaultValueDeclarationSQL($column); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsColumnCollation() { return true; } /** * {@inheritDoc} */ public function getJsonTypeDeclarationSQL(array $column) { if (! empty($column['jsonb'])) { return 'JSONB'; } return 'JSON'; } private function getOldColumnComment(ColumnDiff $columnDiff): ?string { $oldColumn = $columnDiff->getOldColumn(); if ($oldColumn !== null) { return $this->getColumnComment($oldColumn); } return null; } /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ public function getListTableMetadataSQL(string $table, ?string $schema = null): string { if ($schema !== null) { $table = $schema . '.' . $table; } return sprintf( <<<'SQL' SELECT obj_description(%s::regclass) AS table_comment; SQL , $this->quoteStringLiteral($table), ); } public function createSchemaManager(Connection $connection): PostgreSQLSchemaManager { return new PostgreSQLSchemaManager($connection, $this); } } dbal/src/Platforms/MariaDb1043Platform.php 0000644 00000011706 15021222234 0014227 0 ustar 00 <?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); } } dbal/src/Platforms/DB2111Platform.php 0000644 00000002132 15021222234 0013203 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Exception; use function sprintf; /** * Provides the behavior, features and SQL dialect of the IBM DB2 11.1 (11.1 GA) database platform. * * @deprecated This class will be merged with {@see DB2Platform} in 4.0 because support for IBM DB2 * releases prior to 11.1 will be dropped. * * @see https://www.ibm.com/docs/en/db2/11.1?topic=database-whats-new-db2-version-111-ga */ class DB2111Platform extends DB2Platform { /** * {@inheritDoc} * * @see https://www.ibm.com/docs/en/db2/11.1?topic=subselect-fetch-clause */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($offset > 0) { $query .= sprintf(' OFFSET %u ROWS', $offset); } if ($limit !== null) { if ($limit < 0) { throw new Exception(sprintf('Limit must be a positive integer or zero, %d given', $limit)); } $query .= sprintf(' FETCH %s %u ROWS ONLY', $offset === 0 ? 'FIRST' : 'NEXT', $limit); } return $query; } } dbal/src/Platforms/MySQL80Platform.php 0000644 00000001657 15021222234 0013541 0 ustar 00 <?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(); } } dbal/src/Platforms/PostgreSQL100Platform.php 0000644 00000002245 15021222234 0014642 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Platforms\Keywords\PostgreSQL100Keywords; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\Deprecations\Deprecation; /** * Provides the behavior, features and SQL dialect of the PostgreSQL 10.0 database platform. * * @deprecated This class will be merged with {@see PostgreSQLPlatform} in 4.0 because support for Postgres * releases prior to 10.0 will be dropped. */ class PostgreSQL100Platform extends PostgreSQL94Platform { /** @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass(): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'PostgreSQL100Platform::getReservedKeywordsClass() is deprecated,' . ' use PostgreSQL100Platform::createReservedKeywordsList() instead.', ); return PostgreSQL100Keywords::class; } public function createSelectSQLBuilder(): SelectSQLBuilder { return AbstractPlatform::createSelectSQLBuilder(); } } dbal/src/Platforms/MariaDb1027Platform.php 0000644 00000000547 15021222234 0014232 0 ustar 00 <?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 { } dbal/src/Platforms/MySQL84Platform.php 0000644 00000001370 15021222234 0013535 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\Deprecations\Deprecation; /** * Provides the behavior, features and SQL dialect of the MySQL 8.4 database platform. */ class MySQL84Platform extends MySQL80Platform { /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'MySQL84Platform::getReservedKeywordsClass() is deprecated,' . ' use MySQL84Platform::createReservedKeywordsList() instead.', ); return Keywords\MySQL84Keywords::class; } } dbal/src/Platforms/PostgreSQL94Platform.php 0000644 00000000403 15021222234 0014570 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; /** * Provides the behavior, features and SQL dialect of the PostgreSQL 9.4+ database platform. * * @deprecated Use {@see PostgreSQLPlatform} instead. */ class PostgreSQL94Platform extends PostgreSQLPlatform { } dbal/src/Platforms/MySQL/CollationMetadataProvider.php 0000644 00000000311 15021222234 0016746 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\MySQL; /** @internal */ interface CollationMetadataProvider { public function getCollationCharset(string $collation): ?string; } dbal/src/Platforms/MySQL/Comparator.php 0000644 00000005334 15021222234 0013767 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\MySQL; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Schema\Comparator as BaseComparator; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use function array_diff_assoc; use function array_intersect_key; /** * Compares schemas in the context of MySQL platform. * * In MySQL, unless specified explicitly, the column's character set and collation are inherited from its containing * table. So during comparison, an omitted value and the value that matches the default value of table in the * desired schema must be considered equal. */ class Comparator extends BaseComparator { /** @var CollationMetadataProvider */ private $collationMetadataProvider; /** @internal The comparator can be only instantiated by a schema manager. */ public function __construct(AbstractMySQLPlatform $platform, CollationMetadataProvider $collationMetadataProvider) { parent::__construct($platform); $this->collationMetadataProvider = $collationMetadataProvider; } public function compareTables(Table $fromTable, Table $toTable): TableDiff { return parent::compareTables( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } /** * {@inheritDoc} */ public function diffTable(Table $fromTable, Table $toTable) { return parent::diffTable( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } private function normalizeColumns(Table $table): Table { $tableOptions = array_intersect_key($table->getOptions(), [ 'charset' => null, 'collation' => null, ]); $table = clone $table; foreach ($table->getColumns() as $column) { $originalOptions = $column->getPlatformOptions(); $normalizedOptions = $this->normalizeOptions($originalOptions); $overrideOptions = array_diff_assoc($normalizedOptions, $tableOptions); if ($overrideOptions === $originalOptions) { continue; } $column->setPlatformOptions($overrideOptions); } return $table; } /** * @param array<string,string> $options * * @return array<string,string> */ private function normalizeOptions(array $options): array { if (isset($options['collation']) && ! isset($options['charset'])) { $charset = $this->collationMetadataProvider->getCollationCharset($options['collation']); if ($charset !== null) { $options['charset'] = $charset; } } return $options; } } dbal/src/Platforms/MySQL/CollationMetadataProvider/CachingCollationMetadataProvider.php 0000644 00000001626 15021222234 0025335 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; use function array_key_exists; /** @internal */ final class CachingCollationMetadataProvider implements CollationMetadataProvider { /** @var CollationMetadataProvider */ private $collationMetadataProvider; /** @var array<string,?string> */ private $cache = []; public function __construct(CollationMetadataProvider $collationMetadataProvider) { $this->collationMetadataProvider = $collationMetadataProvider; } public function getCollationCharset(string $collation): ?string { if (array_key_exists($collation, $this->cache)) { return $this->cache[$collation]; } return $this->cache[$collation] = $this->collationMetadataProvider->getCollationCharset($collation); } } dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php 0000644 00000001613 15021222234 0026074 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; /** @internal */ final class ConnectionCollationMetadataProvider implements CollationMetadataProvider { /** @var Connection */ private $connection; public function __construct(Connection $connection) { $this->connection = $connection; } /** @throws Exception */ public function getCollationCharset(string $collation): ?string { $charset = $this->connection->fetchOne( <<<'SQL' SELECT CHARACTER_SET_NAME FROM information_schema.COLLATIONS WHERE COLLATION_NAME = ?; SQL , [$collation], ); if ($charset !== false) { return $charset; } return null; } } dbal/src/Platforms/SQLite/Comparator.php 0000644 00000003111 15021222234 0014152 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\SQLite; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\Comparator as BaseComparator; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use function strcasecmp; /** * Compares schemas in the context of SQLite platform. * * BINARY is the default column collation and should be ignored if specified explicitly. */ class Comparator extends BaseComparator { /** @internal The comparator can be only instantiated by a schema manager. */ public function __construct(SqlitePlatform $platform) { parent::__construct($platform); } public function compareTables(Table $fromTable, Table $toTable): TableDiff { return parent::compareTables( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } /** * {@inheritDoc} */ public function diffTable(Table $fromTable, Table $toTable) { return parent::diffTable( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } private function normalizeColumns(Table $table): Table { $table = clone $table; foreach ($table->getColumns() as $column) { $options = $column->getPlatformOptions(); if (! isset($options['collation']) || strcasecmp($options['collation'], 'binary') !== 0) { continue; } unset($options['collation']); $column->setPlatformOptions($options); } return $table; } } dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php 0000644 00000004163 15021222234 0021442 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\SQLServer\SQL\Builder; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode; use Doctrine\DBAL\Query\SelectQuery; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use function count; use function implode; final class SQLServerSelectSQLBuilder implements SelectSQLBuilder { private SQLServerPlatform $platform; /** @internal The SQL builder should be instantiated only by database platforms. */ public function __construct(SQLServerPlatform $platform) { $this->platform = $platform; } public function buildSQL(SelectQuery $query): string { $parts = ['SELECT']; if ($query->isDistinct()) { $parts[] = 'DISTINCT'; } $parts[] = implode(', ', $query->getColumns()); $from = $query->getFrom(); if (count($from) > 0) { $parts[] = 'FROM ' . implode(', ', $from); } $forUpdate = $query->getForUpdate(); if ($forUpdate !== null) { $with = ['UPDLOCK', 'ROWLOCK']; if ($forUpdate->getConflictResolutionMode() === ConflictResolutionMode::SKIP_LOCKED) { $with[] = 'READPAST'; } $parts[] = 'WITH (' . implode(', ', $with) . ')'; } $where = $query->getWhere(); if ($where !== null) { $parts[] = 'WHERE ' . $where; } $groupBy = $query->getGroupBy(); if (count($groupBy) > 0) { $parts[] = 'GROUP BY ' . implode(', ', $groupBy); } $having = $query->getHaving(); if ($having !== null) { $parts[] = 'HAVING ' . $having; } $orderBy = $query->getOrderBy(); if (count($orderBy) > 0) { $parts[] = 'ORDER BY ' . implode(', ', $orderBy); } $sql = implode(' ', $parts); $limit = $query->getLimit(); if ($limit->isDefined()) { $sql = $this->platform->modifyLimitQuery($sql, $limit->getMaxResults(), $limit->getFirstResult()); } return $sql; } } dbal/src/Platforms/SQLServer/Comparator.php 0000644 00000003277 15021222234 0014654 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\SQLServer; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Schema\Comparator as BaseComparator; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; /** * Compares schemas in the context of SQL Server platform. * * @link https://docs.microsoft.com/en-us/sql/t-sql/statements/collations?view=sql-server-ver15 */ class Comparator extends BaseComparator { private string $databaseCollation; /** @internal The comparator can be only instantiated by a schema manager. */ public function __construct(SQLServerPlatform $platform, string $databaseCollation) { parent::__construct($platform); $this->databaseCollation = $databaseCollation; } public function compareTables(Table $fromTable, Table $toTable): TableDiff { return parent::compareTables( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } /** * {@inheritDoc} */ public function diffTable(Table $fromTable, Table $toTable) { return parent::diffTable( $this->normalizeColumns($fromTable), $this->normalizeColumns($toTable), ); } private function normalizeColumns(Table $table): Table { $table = clone $table; foreach ($table->getColumns() as $column) { $options = $column->getPlatformOptions(); if (! isset($options['collation']) || $options['collation'] !== $this->databaseCollation) { continue; } unset($options['collation']); $column->setPlatformOptions($options); } return $table; } } dbal/src/Platforms/MariaDb1010Platform.php 0000644 00000002702 15021222234 0014215 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use function implode; /** * Provides the behavior, features and SQL dialect of the MariaDB 10.10 database platform. */ class MariaDb1010Platform extends MariaDb1060Platform { public function createSelectSQLBuilder(): SelectSQLBuilder { return AbstractPlatform::createSelectSQLBuilder(); } public function fetchTableOptionsByTable(bool $includeTableName): string { // MariaDB-10.10.1 added FULL_COLLATION_NAME to the information_schema.COLLATION_CHARACTER_SET_APPLICABILITY. // A base collation like uca1400_ai_ci can refer to multiple character sets. The value in the // information_schema.TABLES.TABLE_COLLATION corresponds to the full collation name. $sql = <<<'SQL' SELECT t.TABLE_NAME, t.ENGINE, t.AUTO_INCREMENT, t.TABLE_COMMENT, t.CREATE_OPTIONS, t.TABLE_COLLATION, ccsa.CHARACTER_SET_NAME FROM information_schema.TABLES t INNER JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY ccsa ON ccsa.FULL_COLLATION_NAME = t.TABLE_COLLATION SQL; $conditions = ['t.TABLE_SCHEMA = ?']; if ($includeTableName) { $conditions[] = 't.TABLE_NAME = ?'; } $conditions[] = "t.TABLE_TYPE = 'BASE TABLE'"; return $sql . ' WHERE ' . implode(' AND ', $conditions); } } dbal/src/Platforms/Keywords/ReservedKeywordsValidator.php 0000644 00000005643 15021222234 0017702 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\Deprecations\Deprecation; use function count; use function implode; use function str_replace; /** @deprecated Use database documentation instead. */ class ReservedKeywordsValidator implements Visitor { /** @var KeywordList[] */ private array $keywordLists; /** @var string[] */ private array $violations = []; /** @param KeywordList[] $keywordLists */ public function __construct(array $keywordLists) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5431', 'ReservedKeywordsValidator is deprecated. Use database documentation instead.', ); $this->keywordLists = $keywordLists; } /** @return string[] */ public function getViolations() { return $this->violations; } /** * @param string $word * * @return string[] */ private function isReservedWord($word): array { if ($word[0] === '`') { $word = str_replace('`', '', $word); } $keywordLists = []; foreach ($this->keywordLists as $keywordList) { if (! $keywordList->isKeyword($word)) { continue; } $keywordLists[] = $keywordList->getName(); } return $keywordLists; } /** * @param string $asset * @param string[] $violatedPlatforms */ private function addViolation($asset, $violatedPlatforms): void { if (count($violatedPlatforms) === 0) { return; } $this->violations[] = $asset . ' keyword violations: ' . implode(', ', $violatedPlatforms); } /** * {@inheritDoc} */ public function acceptColumn(Table $table, Column $column) { $this->addViolation( 'Table ' . $table->getName() . ' column ' . $column->getName(), $this->isReservedWord($column->getName()), ); } /** * {@inheritDoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { } /** * {@inheritDoc} */ public function acceptIndex(Table $table, Index $index) { } /** * {@inheritDoc} */ public function acceptSchema(Schema $schema) { } /** * {@inheritDoc} */ public function acceptSequence(Sequence $sequence) { } /** * {@inheritDoc} */ public function acceptTable(Table $table) { $this->addViolation( 'Table ' . $table->getName(), $this->isReservedWord($table->getName()), ); } } dbal/src/Platforms/Keywords/SQLiteKeywords.php 0000644 00000006253 15021222234 0015414 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * SQLite Keywordlist. */ class SQLiteKeywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'SQLiteKeywords::getName() is deprecated.', ); return 'SQLite'; } /** * {@inheritDoc} */ protected function getKeywords() { return [ 'ABORT', 'ACTION', 'ADD', 'AFTER', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ATTACH', 'AUTOINCREMENT', 'BEFORE', 'BEGIN', 'BETWEEN', 'BY', 'CASCADE', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLUMN', 'COMMIT', 'CONFLICT', 'CONSTRAINT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'DATABASE', 'DEFAULT', 'DEFERRABLE', 'DEFERRED', 'DELETE', 'DESC', 'DETACH', 'DISTINCT', 'DROP', 'EACH', 'ELSE', 'END', 'ESCAPE', 'EXCEPT', 'EXCLUSIVE', 'EXISTS', 'EXPLAIN', 'FAIL', 'FOR', 'FOREIGN', 'FROM', 'FULL', 'GLOB', 'GROUP', 'HAVING', 'IF', 'IGNORE', 'IMMEDIATE', 'IN', 'INDEX', 'INDEXED', 'INITIALLY', 'INNER', 'INSERT', 'INSTEAD', 'INTERSECT', 'INTO', 'IS', 'ISNULL', 'JOIN', 'KEY', 'LEFT', 'LIKE', 'LIMIT', 'MATCH', 'NATURAL', 'NO', 'NOT', 'NOTNULL', 'NULL', 'OF', 'OFFSET', 'ON', 'OR', 'ORDER', 'OUTER', 'PLAN', 'PRAGMA', 'PRIMARY', 'QUERY', 'RAISE', 'REFERENCES', 'REGEXP', 'REINDEX', 'RELEASE', 'RENAME', 'REPLACE', 'RESTRICT', 'RIGHT', 'ROLLBACK', 'ROW', 'SAVEPOINT', 'SELECT', 'SET', 'TABLE', 'TEMP', 'TEMPORARY', 'THEN', 'TO', 'TRANSACTION', 'TRIGGER', 'UNION', 'UNIQUE', 'UPDATE', 'USING', 'VACUUM', 'VALUES', 'VIEW', 'VIRTUAL', 'WHEN', 'WHERE', ]; } } dbal/src/Platforms/Keywords/SQLServer2012Keywords.php 0000644 00000000350 15021222234 0016376 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * Microsoft SQL Server 2012 reserved keyword dictionary. * * @deprecated Use {@see SQLServerKeywords} instead. */ class SQLServer2012Keywords extends SQLServerKeywords { } dbal/src/Platforms/Keywords/MySQLKeywords.php 0000644 00000013723 15021222234 0015220 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * MySQL Keywordlist. */ class MySQLKeywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MySQLKeywords::getName() is deprecated.', ); return 'MySQL'; } /** * {@inheritDoc} */ protected function getKeywords() { return [ 'ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'CONDITION', 'CONNECTION', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'ESCAPED', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'GENERAL', 'GOTO', 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IGNORE_SERVER_IDS', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERVAL', 'INTO', 'IS', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LABEL', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_HEARTBEAT_PERIOD', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NO_WRITE_TO_BINLOG', 'NOT', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'PARTITION', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'RAID0', 'RANGE', 'READ', 'READ_WRITE', 'READS', 'REAL', 'RECURSIVE', 'REFERENCES', 'REGEXP', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'RLIKE', 'ROWS', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SLOW', 'SMALLINT', 'SONAME', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SSL', 'STARTING', 'STRAIGHT_JOIN', 'TABLE', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WRITE', 'X509', 'XOR', 'YEAR_MONTH', 'ZEROFILL', ]; } } dbal/src/Platforms/Keywords/MariaDBKeywords.php 0000644 00000014172 15021222234 0015511 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; class MariaDBKeywords extends MySQLKeywords { /** @deprecated */ public function getName(): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MariaDBKeywords::getName() is deprecated.', ); return 'MariaDB'; } /** * {@inheritDoc} */ protected function getKeywords(): array { return [ 'ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'CONDITION', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'ESCAPED', 'EXCEPT', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'GENERATED', 'GET', 'GENERAL', 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IGNORE_SERVER_IDS', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERSECT', 'INTERVAL', 'INTO', 'IO_AFTER_GTIDS', 'IO_BEFORE_GTIDS', 'IS', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_BIND', 'MASTER_HEARTBEAT_PERIOD', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NO_WRITE_TO_BINLOG', 'NOT', 'NULL', 'NUMERIC', 'OFFSET', 'ON', 'OPTIMIZE', 'OPTIMIZER_COSTS', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'OVER', 'PARTITION', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'RANGE', 'READ', 'READ_WRITE', 'READS', 'REAL', 'RECURSIVE', 'REFERENCES', 'REGEXP', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT', 'RETURN', 'RETURNING', 'REVOKE', 'RIGHT', 'RLIKE', 'ROWS', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SLOW', 'SMALLINT', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SSL', 'STARTING', 'STORED', 'STRAIGHT_JOIN', 'TABLE', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'VIRTUAL', 'WHEN', 'WHERE', 'WHILE', 'WINDOW', 'WITH', 'WRITE', 'XOR', 'YEAR_MONTH', 'ZEROFILL', ]; } } dbal/src/Platforms/Keywords/MySQL84Keywords.php 0000644 00000002466 15021222234 0015376 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; use function array_diff; use function array_merge; /** * MySQL 8.4 reserved keywords list. */ class MySQL84Keywords extends MySQL80Keywords { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MySQL84Keywords::getName() is deprecated.', ); return 'MySQL84'; } /** * {@inheritDoc} * * @link https://dev.mysql.com/doc/refman/8.4/en/keywords.html */ protected function getKeywords() { $keywords = parent::getKeywords(); // Removed Keywords and Reserved Words $keywords = array_diff($keywords, [ 'MASTER_BIND', 'MASTER_SSL_VERIFY_SERVER_CERT', ]); // New Keywords and Reserved Words $keywords = array_merge($keywords, [ 'AUTO', 'BERNOULLI', 'GTIDS', 'LOG', 'MANUAL', 'PARALLEL', 'PARSE_TREE', 'QUALIFY', 'S3', 'TABLESAMPLE', ]); return $keywords; } } dbal/src/Platforms/Keywords/OracleKeywords.php 0000644 00000005753 15021222234 0015464 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * Oracle Keywordlist. */ class OracleKeywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'OracleKeywords::getName() is deprecated.', ); return 'Oracle'; } /** * {@inheritDoc} */ protected function getKeywords() { return [ 'ACCESS', 'ADD', 'ALL', 'ALTER', 'AND', 'ANY', 'ARRAYLEN', 'AS', 'ASC', 'AUDIT', 'BETWEEN', 'BY', 'CHAR', 'CHECK', 'CLUSTER', 'COLUMN', 'COMMENT', 'COMPRESS', 'CONNECT', 'CREATE', 'CURRENT', 'DATE', 'DECIMAL', 'DEFAULT', 'DELETE', 'DESC', 'DISTINCT', 'DROP', 'ELSE', 'EXCLUSIVE', 'EXISTS', 'FILE', 'FLOAT', 'FOR', 'FROM', 'GRANT', 'GROUP', 'HAVING', 'IDENTIFIED', 'IMMEDIATE', 'IN', 'INCREMENT', 'INDEX', 'INITIAL', 'INSERT', 'INTEGER', 'INTERSECT', 'INTO', 'IS', 'LEVEL', 'LIKE', 'LOCK', 'LONG', 'MAXEXTENTS', 'MINUS', 'MODE', 'MODIFY', 'NOAUDIT', 'NOCOMPRESS', 'NOT', 'NOTFOUND', 'NOWAIT', 'NULL', 'NUMBER', 'OF', 'OFFLINE', 'ON', 'ONLINE', 'OPTION', 'OR', 'ORDER', 'PCTFREE', 'PRIOR', 'PRIVILEGES', 'PUBLIC', 'RANGE', 'RAW', 'RENAME', 'RESOURCE', 'REVOKE', 'ROW', 'ROWID', 'ROWLABEL', 'ROWNUM', 'ROWS', 'SELECT', 'SESSION', 'SET', 'SHARE', 'SIZE', 'SMALLINT', 'SQLBUF', 'START', 'SUCCESSFUL', 'SYNONYM', 'SYSDATE', 'TABLE', 'THEN', 'TO', 'TRIGGER', 'UID', 'UNION', 'UNIQUE', 'UPDATE', 'USER', 'VALIDATE', 'VALUES', 'VARCHAR', 'VARCHAR2', 'VIEW', 'WHENEVER', 'WHERE', 'WITH', ]; } } dbal/src/Platforms/Keywords/PostgreSQLKeywords.php 0000644 00000005553 15021222234 0016260 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * Reserved keywords list corresponding to the PostgreSQL database platform of the oldest supported version. */ class PostgreSQLKeywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'PostgreSQLKeywords::getName() is deprecated.', ); return 'PostgreSQL'; } /** * {@inheritDoc} */ protected function getKeywords() { return [ 'ALL', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'ARRAY', 'AS', 'ASC', 'ASYMMETRIC', 'AUTHORIZATION', 'BINARY', 'BOTH', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLLATION', 'COLUMN', 'CONCURRENTLY', 'CONSTRAINT', 'CREATE', 'CROSS', 'CURRENT_CATALOG', 'CURRENT_DATE', 'CURRENT_ROLE', 'CURRENT_SCHEMA', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'DEFAULT', 'DEFERRABLE', 'DESC', 'DISTINCT', 'DO', 'ELSE', 'END', 'EXCEPT', 'FALSE', 'FETCH', 'FOR', 'FOREIGN', 'FREEZE', 'FROM', 'FULL', 'GRANT', 'GROUP', 'HAVING', 'ILIKE', 'IN', 'INITIALLY', 'INNER', 'INTERSECT', 'INTO', 'IS', 'ISNULL', 'JOIN', 'LATERAL', 'LEADING', 'LEFT', 'LIKE', 'LIMIT', 'LOCALTIME', 'LOCALTIMESTAMP', 'NATURAL', 'NOT', 'NOTNULL', 'NULL', 'OFFSET', 'ON', 'ONLY', 'OR', 'ORDER', 'OUTER', 'OVERLAPS', 'PLACING', 'PRIMARY', 'REFERENCES', 'RETURNING', 'RIGHT', 'SELECT', 'SESSION_USER', 'SIMILAR', 'SOME', 'SYMMETRIC', 'TABLE', 'THEN', 'TO', 'TRAILING', 'TRUE', 'UNION', 'UNIQUE', 'USER', 'USING', 'VARIADIC', 'VERBOSE', 'WHEN', 'WHERE', 'WINDOW', 'WITH', ]; } } dbal/src/Platforms/Keywords/MySQL57Keywords.php 0000644 00000014106 15021222234 0015370 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * MySQL 5.7 reserved keywords list. * * @deprecated Use {@link MySQLKeywords} instead. */ class MySQL57Keywords extends MySQLKeywords { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MySQL57Keywords::getName() is deprecated.', ); return 'MySQL57'; } /** * {@inheritDoc} * * @link http://dev.mysql.com/doc/mysqld-version-reference/en/mysqld-version-reference-reservedwords-5-7.html */ protected function getKeywords() { return [ 'ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'CONDITION', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'ESCAPED', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'GENERATED', 'GET', 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERVAL', 'INTO', 'IO_AFTER_GTIDS', 'IO_BEFORE_GTIDS', 'IS', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_BIND', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NO_WRITE_TO_BINLOG', 'NOT', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE', 'OPTIMIZER_COSTS', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'PARTITION', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'RANGE', 'READ', 'READ_WRITE', 'READS', 'REAL', 'REFERENCES', 'REGEXP', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'RLIKE', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SMALLINT', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SSL', 'STARTING', 'STORED', 'STRAIGHT_JOIN', 'TABLE', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'VIRTUAL', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WRITE', 'XOR', 'YEAR_MONTH', 'ZEROFILL', ]; } } dbal/src/Platforms/Keywords/KeywordList.php 0000644 00000002165 15021222234 0015001 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_flip; use function array_map; use function strtoupper; /** * Abstract interface for a SQL reserved keyword dictionary. * * @psalm-consistent-constructor */ abstract class KeywordList { /** @var string[]|null */ private ?array $keywords = null; /** * Checks if the given word is a keyword of this dialect/vendor platform. * * @param string $word * * @return bool */ public function isKeyword($word) { if ($this->keywords === null) { $this->initializeKeywords(); } return isset($this->keywords[strtoupper($word)]); } /** @return void */ protected function initializeKeywords() { $this->keywords = array_flip(array_map('strtoupper', $this->getKeywords())); } /** * Returns the list of keywords. * * @return string[] */ abstract protected function getKeywords(); /** * Returns the name of this keyword list. * * @deprecated * * @return string */ abstract public function getName(); } dbal/src/Platforms/Keywords/SQLServerKeywords.php 0000644 00000011723 15021222234 0016077 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * Microsoft SQL Server 2012 reserved keyword dictionary. * Reserved keywords list corresponding to the Microsoft SQL Server database platform of the oldest supported version. */ class SQLServerKeywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'SQLServerKeywords::getName() is deprecated.', ); return 'SQLServer'; } /** * {@inheritDoc} * * @link http://msdn.microsoft.com/en-us/library/aa238507%28v=sql.80%29.aspx */ protected function getKeywords() { return [ 'ADD', 'ALL', 'ALTER', 'AND', 'ANY', 'AS', 'ASC', 'AUTHORIZATION', 'BACKUP', 'BEGIN', 'BETWEEN', 'BREAK', 'BROWSE', 'BULK', 'BY', 'CASCADE', 'CASE', 'CHECK', 'CHECKPOINT', 'CLOSE', 'CLUSTERED', 'COALESCE', 'COLLATE', 'COLUMN', 'COMMIT', 'COMPUTE', 'CONSTRAINT', 'CONTAINS', 'CONTAINSTABLE', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DBCC', 'DEALLOCATE', 'DECLARE', 'DEFAULT', 'DELETE', 'DENY', 'DESC', 'DISK', 'DISTINCT', 'DISTRIBUTED', 'DOUBLE', 'DROP', 'DUMP', 'ELSE', 'END', 'ERRLVL', 'ESCAPE', 'EXCEPT', 'EXEC', 'EXECUTE', 'EXISTS', 'EXIT', 'EXTERNAL', 'FETCH', 'FILE', 'FILLFACTOR', 'FOR', 'FOREIGN', 'FREETEXT', 'FREETEXTTABLE', 'FROM', 'FULL', 'FUNCTION', 'GOTO', 'GRANT', 'GROUP', 'HAVING', 'HOLDLOCK', 'IDENTITY', 'IDENTITY_INSERT', 'IDENTITYCOL', 'IF', 'IN', 'INDEX', 'INNER', 'INSERT', 'INTERSECT', 'INTO', 'IS', 'JOIN', 'KEY', 'KILL', 'LEFT', 'LIKE', 'LINENO', 'LOAD', 'MERGE', 'NATIONAL', 'NOCHECK ', 'NONCLUSTERED', 'NOT', 'NULL', 'NULLIF', 'OF', 'OFF', 'OFFSETS', 'ON', 'OPEN', 'OPENDATASOURCE', 'OPENQUERY', 'OPENROWSET', 'OPENXML', 'OPTION', 'OR', 'ORDER', 'OUTER', 'OVER', 'PERCENT', 'PIVOT', 'PLAN', 'PRECISION', 'PRIMARY', 'PRINT', 'PROC', 'PROCEDURE', 'PUBLIC', 'RAISERROR', 'READ', 'READTEXT', 'RECONFIGURE', 'REFERENCES', 'REPLICATION', 'RESTORE', 'RESTRICT', 'RETURN', 'REVERT', 'REVOKE', 'RIGHT', 'ROLLBACK', 'ROWCOUNT', 'ROWGUIDCOL', 'RULE', 'SAVE', 'SCHEMA', 'SECURITYAUDIT', 'SELECT', 'SEMANTICKEYPHRASETABLE', 'SEMANTICSIMILARITYDETAILSTABLE', 'SEMANTICSIMILARITYTABLE', 'SESSION_USER', 'SET', 'SETUSER', 'SHUTDOWN', 'SOME', 'STATISTICS', 'SYSTEM_USER', 'TABLE', 'TABLESAMPLE', 'TEXTSIZE', 'THEN', 'TO', 'TOP', 'TRAN', 'TRANSACTION', 'TRIGGER', 'TRUNCATE', 'TRY_CONVERT', 'TSEQUAL', 'UNION', 'UNIQUE', 'UNPIVOT', 'UPDATE', 'UPDATETEXT', 'USE', 'USER', 'VALUES', 'VARYING', 'VIEW', 'WAITFOR', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WITHIN GROUP', 'WRITETEXT', ]; } } dbal/src/Platforms/Keywords/PostgreSQL94Keywords.php 0000644 00000000331 15021222234 0016422 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * PostgreSQL 9.4 reserved keywords list. * * @deprecated Use {@see PostgreSQLKeywords} instead. */ class PostgreSQL94Keywords extends PostgreSQLKeywords { } dbal/src/Platforms/Keywords/MariaDb102Keywords.php 0000644 00000001171 15021222234 0015767 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * MariaDb reserved keywords list. * * @deprecated Use {@link MariaDBKeywords} instead. * * @link https://mariadb.com/kb/en/the-mariadb-library/reserved-words/ */ final class MariaDb102Keywords extends MariaDBKeywords { /** @deprecated */ public function getName(): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MariaDb102Keywords::getName() is deprecated.', ); return 'MariaDb102'; } } dbal/src/Platforms/Keywords/MySQL80Keywords.php 0000644 00000003011 15021222234 0015355 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; use function array_merge; /** * MySQL 8.0 reserved keywords list. */ class MySQL80Keywords extends MySQL57Keywords { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'MySQL80Keywords::getName() is deprecated.', ); return 'MySQL80'; } /** * {@inheritDoc} * * @link https://dev.mysql.com/doc/refman/8.0/en/keywords.html */ protected function getKeywords() { $keywords = parent::getKeywords(); $keywords = array_merge($keywords, [ 'ADMIN', 'ARRAY', 'CUBE', 'CUME_DIST', 'DENSE_RANK', 'EMPTY', 'EXCEPT', 'FIRST_VALUE', 'FUNCTION', 'GROUPING', 'GROUPS', 'JSON_TABLE', 'LAG', 'LAST_VALUE', 'LATERAL', 'LEAD', 'MEMBER', 'NTH_VALUE', 'NTILE', 'OF', 'OVER', 'PERCENT_RANK', 'PERSIST', 'PERSIST_ONLY', 'RANK', 'RECURSIVE', 'ROW', 'ROWS', 'ROW_NUMBER', 'SYSTEM', 'WINDOW', ]); return $keywords; } } dbal/src/Platforms/Keywords/PostgreSQL100Keywords.php 0000644 00000001134 15021222234 0016470 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * PostgreSQL 10.0 reserved keywords list. * * @deprecated Use {@link PostgreSQLKeywords} instead. */ class PostgreSQL100Keywords extends PostgreSQL94Keywords { /** @deprecated */ public function getName(): string { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'PostgreSQL100Keywords::getName() is deprecated.', ); return 'PostgreSQL100'; } } dbal/src/Platforms/Keywords/DB2Keywords.php 0000644 00000022633 15021222234 0014622 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\Deprecations\Deprecation; /** * DB2 Keywords. */ class DB2Keywords extends KeywordList { /** * {@inheritDoc} * * @deprecated */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5433', 'DB2Keywords::getName() is deprecated.', ); return 'DB2'; } /** * {@inheritDoc} */ protected function getKeywords() { return [ 'ACTIVATE', 'ADD', 'AFTER', 'ALIAS', 'ALL', 'ALLOCATE', 'ALLOW', 'ALTER', 'AND', 'ANY', 'AS', 'ASENSITIVE', 'ASSOCIATE', 'ASUTIME', 'AT', 'ATTRIBUTES', 'AUDIT', 'AUTHORIZATION', 'AUX', 'AUXILIARY', 'BEFORE', 'BEGIN', 'BETWEEN', 'BINARY', 'BUFFERPOOL', 'BY', 'CACHE', 'CALL', 'CALLED', 'CAPTURE', 'CARDINALITY', 'CASCADED', 'CASE', 'CAST', 'CCSID', 'CHAR', 'CHARACTER', 'CHECK', 'CLONE', 'CLOSE', 'CLUSTER', 'COLLECTION', 'COLLID', 'COLUMN', 'COMMENT', 'COMMIT', 'CONCAT', 'CONDITION', 'CONNECT', 'CONNECTION', 'CONSTRAINT', 'CONTAINS', 'CONTINUE', 'COUNT', 'COUNT_BIG', 'CREATE', 'CROSS', 'CURRENT', 'CURRENT_DATE', 'CURRENT_LC_CTYPE', 'CURRENT_PATH', 'CURRENT_SCHEMA', 'CURRENT_SERVER', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_TIMEZONE', 'CURRENT_USER', 'CURSOR', 'CYCLE', 'DATA', 'DATABASE', 'DATAPARTITIONNAME', 'DATAPARTITIONNUM', 'DATE', 'DAY', 'DAYS', 'DB2GENERAL', 'DB2GENRL', 'DB2SQL', 'DBINFO', 'DBPARTITIONNAME', 'DBPARTITIONNUM', 'DEALLOCATE', 'DECLARE', 'DEFAULT', 'DEFAULTS', 'DEFINITION', 'DELETE', 'DENSE_RANK', 'DENSERANK', 'DESCRIBE', 'DESCRIPTOR', 'DETERMINISTIC', 'DIAGNOSTICS', 'DISABLE', 'DISALLOW', 'DISCONNECT', 'DISTINCT', 'DO', 'DOCUMENT', 'DOUBLE', 'DROP', 'DSSIZE', 'DYNAMIC', 'EACH', 'EDITPROC', 'ELSE', 'ELSEIF', 'ENABLE', 'ENCODING', 'ENCRYPTION', 'END', 'END-EXEC', 'ENDING', 'ERASE', 'ESCAPE', 'EVERY', 'EXCEPT', 'EXCEPTION', 'EXCLUDING', 'EXCLUSIVE', 'EXECUTE', 'EXISTS', 'EXIT', 'EXPLAIN', 'EXTERNAL', 'EXTRACT', 'FENCED', 'FETCH', 'FIELDPROC', 'FILE', 'FINAL', 'FOR', 'FOREIGN', 'FREE', 'FROM', 'FULL', 'FUNCTION', 'GENERAL', 'GENERATED', 'GET', 'GLOBAL', 'GO', 'GOTO', 'GRANT', 'GRAPHIC', 'GROUP', 'HANDLER', 'HASH', 'HASHED_VALUE', 'HAVING', 'HINT', 'HOLD', 'HOUR', 'HOURS', 'IDENTITY', 'IF', 'IMMEDIATE', 'IN', 'INCLUDING', 'INCLUSIVE', 'INCREMENT', 'INDEX', 'INDICATOR', 'INF', 'INFINITY', 'INHERIT', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INTEGRITY', 'INTERSECT', 'INTO', 'IS', 'ISOBID', 'ISOLATION', 'ITERATE', 'JAR', 'JAVA', 'JOIN', 'KEEP', 'KEY', 'LABEL', 'LANGUAGE', 'LATERAL', 'LC_CTYPE', 'LEAVE', 'LEFT', 'LIKE', 'LINKTYPE', 'LOCAL', 'LOCALDATE', 'LOCALE', 'LOCALTIME', 'LOCALTIMESTAMP RIGHT', 'LOCATOR', 'LOCATORS', 'LOCK', 'LOCKMAX', 'LOCKSIZE', 'LONG', 'LOOP', 'MAINTAINED', 'MATERIALIZED', 'MAXVALUE', 'MICROSECOND', 'MICROSECONDS', 'MINUTE', 'MINUTES', 'MINVALUE', 'MODE', 'MODIFIES', 'MONTH', 'MONTHS', 'NAN', 'NEW', 'NEW_TABLE', 'NEXTVAL', 'NO', 'NOCACHE', 'NOCYCLE', 'NODENAME', 'NODENUMBER', 'NOMAXVALUE', 'NOMINVALUE', 'NONE', 'NOORDER', 'NORMALIZED', 'NOT', 'NULL', 'NULLS', 'NUMPARTS', 'OBID', 'OF', 'OLD', 'OLD_TABLE', 'ON', 'OPEN', 'OPTIMIZATION', 'OPTIMIZE', 'OPTION', 'OR', 'ORDER', 'OUT', 'OUTER', 'OVER', 'OVERRIDING', 'PACKAGE', 'PADDED', 'PAGESIZE', 'PARAMETER', 'PART', 'PARTITION', 'PARTITIONED', 'PARTITIONING', 'PARTITIONS', 'PASSWORD', 'PATH', 'PIECESIZE', 'PLAN', 'POSITION', 'PRECISION', 'PREPARE', 'PREVVAL', 'PRIMARY', 'PRIQTY', 'PRIVILEGES', 'PROCEDURE', 'PROGRAM', 'PSID', 'PUBLIC', 'QUERY', 'QUERYNO', 'RANGE', 'RANK', 'READ', 'READS', 'RECOVERY', 'REFERENCES', 'REFERENCING', 'REFRESH', 'RELEASE', 'RENAME', 'REPEAT', 'RESET', 'RESIGNAL', 'RESTART', 'RESTRICT', 'RESULT', 'RESULT_SET_LOCATOR WLM', 'RETURN', 'RETURNS', 'REVOKE', 'ROLE', 'ROLLBACK', 'ROUND_CEILING', 'ROUND_DOWN', 'ROUND_FLOOR', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN', 'ROUND_HALF_UP', 'ROUND_UP', 'ROUTINE', 'ROW', 'ROW_NUMBER', 'ROWNUMBER', 'ROWS', 'ROWSET', 'RRN', 'RUN', 'SAVEPOINT', 'SCHEMA', 'SCRATCHPAD', 'SCROLL', 'SEARCH', 'SECOND', 'SECONDS', 'SECQTY', 'SECURITY', 'SELECT', 'SENSITIVE', 'SEQUENCE', 'SESSION', 'SESSION_USER', 'SET', 'SIGNAL', 'SIMPLE', 'SNAN', 'SOME', 'SOURCE', 'SPECIFIC', 'SQL', 'SQLID', 'STACKED', 'STANDARD', 'START', 'STARTING', 'STATEMENT', 'STATIC', 'STATMENT', 'STAY', 'STOGROUP', 'STORES', 'STYLE', 'SUBSTRING', 'SUMMARY', 'SYNONYM', 'SYSFUN', 'SYSIBM', 'SYSPROC', 'SYSTEM', 'SYSTEM_USER', 'TABLE', 'TABLESPACE', 'THEN', 'TIME', 'TIMESTAMP', 'TO', 'TRANSACTION', 'TRIGGER', 'TRIM', 'TRUNCATE', 'TYPE', 'UNDO', 'UNION', 'UNIQUE', 'UNTIL', 'UPDATE', 'USAGE', 'USER', 'USING', 'VALIDPROC', 'VALUE', 'VALUES', 'VARIABLE', 'VARIANT', 'VCAT', 'VERSION', 'VIEW', 'VOLATILE', 'VOLUMES', 'WHEN', 'WHENEVER', 'WHERE', 'WHILE', 'WITH', 'WITHOUT', 'WRITE', 'XMLELEMENT', 'XMLEXISTS', 'XMLNAMESPACES', 'YEAR', 'YEARS', ]; } } dbal/src/Platforms/TrimMode.php 0000644 00000000452 15021222234 0012427 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms; final class TrimMode { public const UNSPECIFIED = 0; public const LEADING = 1; public const TRAILING = 2; public const BOTH = 3; /** @codeCoverageIgnore */ private function __construct() { } } dbal/src/Platforms/MariaDb1052Platform.php 0000644 00000001465 15021222234 0014230 0 ustar 00 <?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)]; } } dbal/src/Platforms/OraclePlatform.php 0000644 00000116053 15021222234 0013626 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\OracleSchemaManager; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types\BinaryType; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use function array_merge; use function count; use function explode; use function func_get_arg; use function func_num_args; use function implode; use function preg_match; use function sprintf; use function strlen; use function strpos; use function strtoupper; use function substr; /** * OraclePlatform. */ class OraclePlatform extends AbstractPlatform { /** * Assertion for Oracle identifiers. * * @deprecated * * @link http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm * * @param string $identifier * * @return void * * @throws Exception */ public static function assertValidIdentifier($identifier) { if (preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier) === 0) { throw new Exception('Invalid Oracle identifier'); } } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length !== null) { return sprintf('SUBSTR(%s, %d, %d)', $string, $start, $length); } return sprintf('SUBSTR(%s, %d)', $string, $start); } /** * @deprecated Generate dates within the application. * * @param string $type * * @return string */ public function getNowExpression($type = 'timestamp') { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4753', 'OraclePlatform::getNowExpression() is deprecated. Generate dates within the application.', ); switch ($type) { case 'date': case 'time': case 'timestamp': default: return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')'; } } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'INSTR(' . $str . ', ' . $substr . ')'; } return 'INSTR(' . $str . ', ' . $substr . ', ' . $startPos . ')'; } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { switch ($unit) { case DateIntervalUnit::MONTH: case DateIntervalUnit::QUARTER: case DateIntervalUnit::YEAR: switch ($unit) { case DateIntervalUnit::QUARTER: $interval = $this->multiplyInterval((string) $interval, 3); break; case DateIntervalUnit::YEAR: $interval = $this->multiplyInterval((string) $interval, 12); break; } return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')'; default: $calculationClause = ''; switch ($unit) { case DateIntervalUnit::SECOND: $calculationClause = '/24/60/60'; break; case DateIntervalUnit::MINUTE: $calculationClause = '/24/60'; break; case DateIntervalUnit::HOUR: $calculationClause = '/24'; break; case DateIntervalUnit::WEEK: $calculationClause = '*7'; break; } return '(' . $date . $operator . $interval . $calculationClause . ')'; } } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return sprintf('TRUNC(%s) - TRUNC(%s)', $date1, $date2); } /** * {@inheritDoc} */ public function getBitAndComparisonExpression($value1, $value2) { return 'BITAND(' . $value1 . ', ' . $value2 . ')'; } public function getCurrentDatabaseExpression(): string { return "SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')"; } /** * {@inheritDoc} */ public function getBitOrComparisonExpression($value1, $value2) { return '(' . $value1 . '-' . $this->getBitAndComparisonExpression($value1, $value2) . '+' . $value2 . ')'; } /** * {@inheritDoc} */ public function getCreatePrimaryKeySQL(Index $index, $table): string { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } return 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $index->getQuotedName($this) . ' PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; } /** * {@inheritDoc} * * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH. * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection * in {@see listSequences()} */ public function getCreateSequenceSQL(Sequence $sequence) { return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . ' START WITH ' . $sequence->getInitialValue() . ' MINVALUE ' . $sequence->getInitialValue() . ' INCREMENT BY ' . $sequence->getAllocationSize() . $this->getSequenceCacheSQL($sequence); } /** * {@inheritDoc} */ public function getAlterSequenceSQL(Sequence $sequence) { return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize() . $this->getSequenceCacheSQL($sequence); } /** * Cache definition for sequences */ private function getSequenceCacheSQL(Sequence $sequence): string { if ($sequence->getCache() === 0) { return ' NOCACHE'; } if ($sequence->getCache() === 1) { return ' NOCACHE'; } if ($sequence->getCache() > 1) { return ' CACHE ' . $sequence->getCache(); } return ''; } /** * {@inheritDoc} */ public function getSequenceNextValSQL($sequence) { return 'SELECT ' . $sequence . '.nextval FROM DUAL'; } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case TransactionIsolationLevel::READ_UNCOMMITTED: return 'READ UNCOMMITTED'; case TransactionIsolationLevel::READ_COMMITTED: return 'READ COMMITTED'; case TransactionIsolationLevel::REPEATABLE_READ: case TransactionIsolationLevel::SERIALIZABLE: return 'SERIALIZABLE'; default: return parent::_getTransactionIsolationLevelSQL($level); } } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'NUMBER(1)'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'NUMBER(10)'; } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'NUMBER(20)'; } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'NUMBER(5)'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0)'; } /** * {@inheritDoc} */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0) WITH TIME ZONE'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { return ''; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default string column length on Oracle is deprecated' . ', specify the length explicitly.', ); } return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(2000)') : ($length > 0 ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)'); } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed/*, $lengthOmitted = false*/) { if ($length <= 0 || (func_num_args() > 2 && func_get_arg(2))) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length on Oracle is deprecated' . ', specify the length explicitly.', ); } return 'RAW(' . ($length > 0 ? $length : $this->getBinaryMaxLength()) . ')'; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'OraclePlatform::getBinaryMaxLength() is deprecated.', ); return 2000; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'CLOB'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListDatabasesSQL() { return 'SELECT username FROM all_users'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListSequencesSQL($database) { $database = $this->normalizeIdentifier($database); $database = $this->quoteStringLiteral($database->getName()); return 'SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ' . 'WHERE SEQUENCE_OWNER = ' . $database; } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $indexes = $options['indexes'] ?? []; $options['indexes'] = []; $sql = parent::_getCreateTableSQL($name, $columns, $options); foreach ($columns as $columnName => $column) { if (isset($column['sequence'])) { $sql[] = $this->getCreateSequenceSQL($column['sequence']); } if ( ! isset($column['autoincrement']) || ! $column['autoincrement'] && (! isset($column['autoinc']) || ! $column['autoinc']) ) { continue; } $sql = array_merge($sql, $this->getCreateAutoincrementSql($columnName, $name)); } foreach ($indexes as $index) { $sql[] = $this->getCreateIndexSQL($index, $name); } return $sql; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html */ public function getListTableIndexesSQL($table, $database = null) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); return "SELECT uind_col.index_name AS name, ( SELECT uind.index_type FROM user_indexes uind WHERE uind.index_name = uind_col.index_name ) AS type, decode( ( SELECT uind.uniqueness FROM user_indexes uind WHERE uind.index_name = uind_col.index_name ), 'NONUNIQUE', 0, 'UNIQUE', 1 ) AS is_unique, uind_col.column_name AS column_name, uind_col.column_position AS column_pos, ( SELECT ucon.constraint_type FROM user_constraints ucon WHERE ucon.index_name = uind_col.index_name AND ucon.table_name = uind_col.table_name ) AS is_primary FROM user_ind_columns uind_col WHERE uind_col.table_name = " . $table . ' ORDER BY uind_col.column_position ASC'; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { return 'SELECT * FROM sys.user_tables'; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return 'SELECT view_name, text FROM sys.user_views'; } /** * @internal The method should be only used from within the OraclePlatform class hierarchy. * * @param string $name * @param string $table * @param int $start * * @return string[] */ public function getCreateAutoincrementSql($name, $table, $start = 1) { $tableIdentifier = $this->normalizeIdentifier($table); $quotedTableName = $tableIdentifier->getQuotedName($this); $unquotedTableName = $tableIdentifier->getName(); $nameIdentifier = $this->normalizeIdentifier($name); $quotedName = $nameIdentifier->getQuotedName($this); $unquotedName = $nameIdentifier->getName(); $sql = []; $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier); $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true); $sql[] = "DECLARE constraints_Count NUMBER; BEGIN SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = '" . $unquotedTableName . "' AND CONSTRAINT_TYPE = 'P'; IF constraints_Count = 0 OR constraints_Count = '' THEN EXECUTE IMMEDIATE '" . $this->getCreateConstraintSQL($idx, $quotedTableName) . "'; END IF; END;"; $sequenceName = $this->getIdentitySequenceName( $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName, $nameIdentifier->isQuoted() ? $quotedName : $unquotedName, ); $sequence = new Sequence($sequenceName, $start); $sql[] = $this->getCreateSequenceSQL($sequence); $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . ' BEFORE INSERT ON ' . $quotedTableName . ' FOR EACH ROW DECLARE last_Sequence NUMBER; last_InsertID NUMBER; BEGIN IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.' . $quotedName . ' = 0) THEN SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL; ELSE SELECT NVL(Last_Number, 0) INTO last_Sequence FROM User_Sequences WHERE Sequence_Name = \'' . $sequence->getName() . '\'; SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL; WHILE (last_InsertID > last_Sequence) LOOP SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; END LOOP; SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; END IF; END;'; return $sql; } /** * @internal The method should be only used from within the OracleSchemaManager class hierarchy. * * Returns the SQL statements to drop the autoincrement for the given table name. * * @param string $table The table name to drop the autoincrement for. * * @return string[] */ public function getDropAutoincrementSql($table) { $table = $this->normalizeIdentifier($table); $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table); $identitySequenceName = $this->getIdentitySequenceName( $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(), '', ); return [ 'DROP TRIGGER ' . $autoincrementIdentifierName, $this->getDropSequenceSQL($identitySequenceName), $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)), ]; } /** * Normalizes the given identifier. * * Uppercases the given identifier if it is not quoted by intention * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers. * * @param string $name The identifier to normalize. */ private function normalizeIdentifier($name): Identifier { $identifier = new Identifier($name); return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name)); } /** * Adds suffix to identifier, * * if the new string exceeds max identifier length, * keeps $suffix, cuts from $identifier as much as the part exceeding. */ private function addSuffix(string $identifier, string $suffix): string { $maxPossibleLengthWithoutSuffix = $this->getMaxIdentifierLength() - strlen($suffix); if (strlen($identifier) > $maxPossibleLengthWithoutSuffix) { $identifier = substr($identifier, 0, $maxPossibleLengthWithoutSuffix); } return $identifier . $suffix; } /** * Returns the autoincrement primary key identifier name for the given table identifier. * * Quotes the autoincrement primary key identifier name * if the given table name is quoted by intention. */ private function getAutoincrementIdentifierName(Identifier $table): string { $identifierName = $this->addSuffix($table->getName(), '_AI_PK'); return $table->isQuoted() ? $this->quoteSingleIdentifier($identifierName) : $identifierName; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableForeignKeysSQL($table) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); return "SELECT alc.constraint_name, alc.DELETE_RULE, cols.column_name \"local_column\", cols.position, ( SELECT r_cols.table_name FROM user_cons_columns r_cols WHERE alc.r_constraint_name = r_cols.constraint_name AND r_cols.position = cols.position ) AS \"references_table\", ( SELECT r_cols.column_name FROM user_cons_columns r_cols WHERE alc.r_constraint_name = r_cols.constraint_name AND r_cols.position = cols.position ) AS \"foreign_column\" FROM user_cons_columns cols JOIN user_constraints alc ON alc.constraint_name = cols.constraint_name AND alc.constraint_type = 'R' AND alc.table_name = " . $table . ' ORDER BY cols.constraint_name ASC, cols.position ASC'; } /** * @deprecated * * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); return 'SELECT * FROM user_constraints WHERE table_name = ' . $table; } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); $tabColumnsTableName = 'user_tab_columns'; $colCommentsTableName = 'user_col_comments'; $tabColumnsOwnerCondition = ''; $colCommentsOwnerCondition = ''; if ($database !== null && $database !== '/') { $database = $this->normalizeIdentifier($database); $database = $this->quoteStringLiteral($database->getName()); $tabColumnsTableName = 'all_tab_columns'; $colCommentsTableName = 'all_col_comments'; $tabColumnsOwnerCondition = ' AND c.owner = ' . $database; $colCommentsOwnerCondition = ' AND d.OWNER = c.OWNER'; } return sprintf( <<<'SQL' SELECT c.*, ( SELECT d.comments FROM %s d WHERE d.TABLE_NAME = c.TABLE_NAME%s AND d.COLUMN_NAME = c.COLUMN_NAME ) AS comments FROM %s c WHERE c.table_name = %s%s ORDER BY c.column_id SQL , $colCommentsTableName, $colCommentsOwnerCondition, $tabColumnsTableName, $table, $tabColumnsOwnerCondition, ); } /** * {@inheritDoc} */ public function getDropForeignKeySQL($foreignKey, $table) { if ($foreignKey instanceof ForeignKeyConstraint) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $foreignKey as a ForeignKeyConstraint object to %s is deprecated.' . ' Pass it as a quoted name instead.', __METHOD__, ); } else { $foreignKey = new Identifier($foreignKey); } if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); } else { $table = new Identifier($table); } $foreignKey = $foreignKey->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $referentialAction = ''; if ($foreignKey->hasOption('onDelete')) { $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); } if ($referentialAction !== '') { return ' ON DELETE ' . $referentialAction; } return ''; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getForeignKeyReferentialActionSQL($action) { $action = strtoupper($action); switch ($action) { case 'RESTRICT': // RESTRICT is not supported, therefore falling back to NO ACTION. case 'NO ACTION': // NO ACTION cannot be declared explicitly, // therefore returning empty string to indicate to OMIT the referential clause. return ''; case 'CASCADE': case 'SET NULL': return $action; default: // SET DEFAULT is not supported, throw exception instead. throw new InvalidArgumentException('Invalid foreign key action: ' . $action); } } /** * {@inheritDoc} */ public function getCreateDatabaseSQL($name) { return 'CREATE USER ' . $name; } /** * {@inheritDoc} */ public function getDropDatabaseSQL($name) { return 'DROP USER ' . $name . ' CASCADE'; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = []; $commentsSQL = []; $columnSql = []; $fields = []; $tableNameSQL = ($diff->getOldTable() ?? $diff->getName($this))->getQuotedName($this); foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); $comment = $this->getColumnComment($column); if ($comment === null || $comment === '') { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $column->getQuotedName($this), $comment, ); } if (count($fields) > 0) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ADD (' . implode(', ', $fields) . ')'; } $fields = []; foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $newColumn = $columnDiff->getNewColumn(); // Do not generate column alteration clause if type is binary and only fixed property has changed. // Oracle only supports binary type columns with variable length. // Avoids unnecessary table alteration statements. if ( $newColumn->getType() instanceof BinaryType && $columnDiff->hasFixedChanged() && count($columnDiff->changedProperties) === 1 ) { continue; } $columnHasChangedComment = $columnDiff->hasCommentChanged(); /** * Do not add query part if only comment has changed */ if (! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) { $newColumnProperties = $newColumn->toArray(); if (! $columnDiff->hasNotNullChanged()) { unset($newColumnProperties['notnull']); } $fields[] = $newColumn->getQuotedName($this) . $this->getColumnDeclarationSQL('', $newColumnProperties); } if (! $columnHasChangedComment) { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $tableNameSQL, $newColumn->getQuotedName($this), $this->getColumnComment($newColumn), ); } if (count($fields) > 0) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY (' . implode(', ', $fields) . ')'; } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); } $fields = []; foreach ($diff->getDroppedColumns() as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $fields[] = $column->getQuotedName($this); } if (count($fields) > 0) { $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP (' . implode(', ', $fields) . ')'; } $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { $sql = array_merge($sql, $commentsSQL); $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $sql[] = sprintf( 'ALTER TABLE %s RENAME TO %s', $tableNameSQL, $newName->getQuotedName($this), ); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff), ); } return array_merge($sql, $tableSql, $columnSql); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getColumnDeclarationSQL($name, array $column) { if (isset($column['columnDefinition'])) { $columnDef = $this->getCustomTypeDeclarationSQL($column); } else { $default = $this->getDefaultValueDeclarationSQL($column); $notnull = ''; if (isset($column['notnull'])) { $notnull = $column['notnull'] ? ' NOT NULL' : ' NULL'; } if (! empty($column['unique'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "unique" column property is deprecated. Use unique constraints instead.', ); $unique = ' ' . $this->getUniqueFieldDeclarationSQL(); } else { $unique = ''; } if (! empty($column['check'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5656', 'The usage of the "check" column property is deprecated.', ); $check = ' ' . $column['check']; } else { $check = ''; } $typeDecl = $column['type']->getSQLDeclaration($column, $this); $columnDef = $typeDecl . $default . $notnull . $unique . $check; } return $name . ' ' . $columnDef; } /** * {@inheritDoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { if (strpos($tableName, '.') !== false) { [$schema] = explode('.', $tableName); $oldIndexName = $schema . '.' . $oldIndexName; } return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)]; } /** * {@inheritDoc} * * @deprecated */ public function usesSequenceEmulatedIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the OraclePlatform class hierarchy. */ public function getIdentitySequenceName($tableName, $columnName) { $table = new Identifier($tableName); // No usage of column name to preserve BC compatibility with <2.5 $identitySequenceName = $this->addSuffix($table->getName(), '_SEQ'); if ($table->isQuoted()) { $identitySequenceName = '"' . $identitySequenceName . '"'; } $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName); return $identitySequenceIdentifier->getQuotedName($this); } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsCommentOnStatement() { return true; } /** * {@inheritDoc} */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4749', 'OraclePlatform::getName() is deprecated. Identify platforms by their class.', ); return 'oracle'; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit === null && $offset <= 0) { return $query; } if (preg_match('/^\s*SELECT/i', $query) === 1) { if (preg_match('/\sFROM\s/i', $query) === 0) { $query .= ' FROM dual'; } $columns = ['a.*']; if ($offset > 0) { $columns[] = 'ROWNUM AS doctrine_rownum'; } $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query); if ($limit !== null) { $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit); } if ($offset > 0) { $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1); } } return $query; } /** * {@inheritDoc} */ public function getCreateTemporaryTableSnippetSQL() { return 'CREATE GLOBAL TEMPORARY TABLE'; } /** * {@inheritDoc} */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:sP'; } /** * {@inheritDoc} */ public function getDateFormatString() { return 'Y-m-d 00:00:00'; } /** * {@inheritDoc} */ public function getTimeFormatString() { return '1900-01-01 H:i:s'; } /** * {@inheritDoc} */ public function getMaxIdentifierLength() { return 30; } /** * {@inheritDoc} */ public function supportsSequences() { return true; } /** * {@inheritDoc} */ public function supportsReleaseSavepoints() { return false; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); } /** * {@inheritDoc} */ public function getDummySelectSQL() { $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; return sprintf('SELECT %s FROM DUAL', $expression); } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'binary_double' => Types::FLOAT, 'binary_float' => Types::FLOAT, 'binary_integer' => Types::BOOLEAN, 'blob' => Types::BLOB, 'char' => Types::STRING, 'clob' => Types::TEXT, 'date' => Types::DATE_MUTABLE, 'float' => Types::FLOAT, 'integer' => Types::INTEGER, 'long' => Types::STRING, 'long raw' => Types::BLOB, 'nchar' => Types::STRING, 'nclob' => Types::TEXT, 'number' => Types::INTEGER, 'nvarchar2' => Types::STRING, 'pls_integer' => Types::BOOLEAN, 'raw' => Types::BINARY, 'rowid' => Types::STRING, 'timestamp' => Types::DATETIME_MUTABLE, 'timestamptz' => Types::DATETIMETZ_MUTABLE, 'urowid' => Types::STRING, 'varchar' => Types::STRING, 'varchar2' => Types::STRING, ]; } /** * {@inheritDoc} */ public function releaseSavePoint($savepoint) { return ''; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'OraclePlatform::getReservedKeywordsClass() is deprecated,' . ' use OraclePlatform::createReservedKeywordsList() instead.', ); return Keywords\OracleKeywords::class; } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'BLOB'; } /** @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. */ public function getListTableCommentsSQL(string $table, ?string $database = null): string { $tableCommentsName = 'user_tab_comments'; $ownerCondition = ''; if ($database !== null && $database !== '/') { $tableCommentsName = 'all_tab_comments'; $ownerCondition = ' AND owner = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($database)->getName(), ); } return sprintf( <<<'SQL' SELECT comments FROM %s WHERE table_name = %s%s SQL , $tableCommentsName, $this->quoteStringLiteral($this->normalizeIdentifier($table)->getName()), $ownerCondition, ); } public function createSchemaManager(Connection $connection): OracleSchemaManager { return new OracleSchemaManager($connection, $this); } } dbal/src/Platforms/MySQLPlatform.php 0000644 00000000346 15021222234 0013363 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; /** * Provides the behavior, features and SQL dialect of the Oracle MySQL database platform * of the oldest supported version. */ class MySQLPlatform extends AbstractMySQLPlatform { } dbal/src/Platforms/SqlitePlatform.php 0000644 00000132070 15021222234 0013657 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\API\SQLite\UserDefinedFunctions; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Constraint; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\SqliteSchemaManager; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types; use Doctrine\DBAL\Types\IntegerType; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use function array_combine; use function array_keys; use function array_merge; use function array_search; use function array_unique; use function array_values; use function count; use function explode; use function implode; use function is_numeric; use function sprintf; use function sqrt; use function str_replace; use function strlen; use function strpos; use function strtolower; use function trim; /** * The SqlitePlatform class describes the specifics and dialects of the SQLite * database platform. * * @todo Rename: SQLitePlatform */ class SqlitePlatform extends AbstractPlatform { private bool $schemaEmulationEnabled = true; /** * {@inheritDoc} */ public function getRegexpExpression() { return 'REGEXP'; } /** * @deprecated Generate dates within the application. * * @param string $type * * @return string */ public function getNowExpression($type = 'timestamp') { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4753', 'SqlitePlatform::getNowExpression() is deprecated. Generate dates within the application.', ); switch ($type) { case 'time': return 'time(\'now\')'; case 'date': return 'date(\'now\')'; case 'timestamp': default: return 'datetime(\'now\')'; } } /** * {@inheritDoc} */ public function getModExpression($expression1, $expression2) { return $expression1 . ' % ' . $expression2; } /** * {@inheritDoc} */ public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) { $trimChar = $char !== false ? ', ' . $char : ''; switch ($mode) { case TrimMode::LEADING: $trimFn = 'LTRIM'; break; case TrimMode::TRAILING: $trimFn = 'RTRIM'; break; default: $trimFn = 'TRIM'; } return $trimFn . '(' . $str . $trimChar . ')'; } /** * {@inheritDoc} * * SQLite only supports the 2 parameter variant of this function */ public function getSubstringExpression($string, $start, $length = null) { if ($length !== null) { return 'SUBSTR(' . $string . ', ' . $start . ', ' . $length . ')'; } return 'SUBSTR(' . $string . ', ' . $start . ', LENGTH(' . $string . '))'; } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false || $startPos === 1 || $startPos === '1') { return 'INSTR(' . $str . ', ' . $substr . ')'; } return 'CASE WHEN INSTR(SUBSTR(' . $str . ', ' . $startPos . '), ' . $substr . ') > 0 THEN INSTR(SUBSTR(' . $str . ', ' . $startPos . '), ' . $substr . ') + ' . $startPos . ' - 1 ELSE 0 END'; } /** * {@inheritDoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { switch ($unit) { case DateIntervalUnit::SECOND: case DateIntervalUnit::MINUTE: case DateIntervalUnit::HOUR: return 'DATETIME(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')"; } switch ($unit) { case DateIntervalUnit::WEEK: $interval = $this->multiplyInterval((string) $interval, 7); $unit = DateIntervalUnit::DAY; break; case DateIntervalUnit::QUARTER: $interval = $this->multiplyInterval((string) $interval, 3); $unit = DateIntervalUnit::MONTH; break; } if (! is_numeric($interval)) { $interval = "' || " . $interval . " || '"; } return 'DATE(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')"; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2); } /** * {@inheritDoc} * * The DBAL doesn't support databases on the SQLite platform. The expression here always returns a fixed string * as an indicator of an implicitly selected database. * * @link https://www.sqlite.org/lang_select.html * @see Connection::getDatabase() */ public function getCurrentDatabaseExpression(): string { return "'main'"; } /** @link https://www2.sqlite.org/cvstrac/wiki?p=UnsupportedSql */ public function createSelectSQLBuilder(): SelectSQLBuilder { return new DefaultSelectSQLBuilder($this, null, null); } /** * {@inheritDoc} */ protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case TransactionIsolationLevel::READ_UNCOMMITTED: return '0'; case TransactionIsolationLevel::READ_COMMITTED: case TransactionIsolationLevel::REPEATABLE_READ: case TransactionIsolationLevel::SERIALIZABLE: return '1'; default: return parent::_getTransactionIsolationLevelSQL($level); } } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} * * @deprecated */ public function prefersIdentityColumns() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/1519', 'SqlitePlatform::prefersIdentityColumns() is deprecated.', ); return true; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'BOOLEAN'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { // SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * @deprecated Use {@see getSmallIntTypeDeclarationSQL()} instead. * * @param array<string, mixed> $column * * @return string */ public function getTinyIntTypeDeclarationSQL(array $column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5511', '%s is deprecated. Use getSmallIntTypeDeclarationSQL() instead.', __METHOD__, ); // SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { // SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * @deprecated Use {@see getIntegerTypeDeclarationSQL()} instead. * * @param array<string, mixed> $column * * @return string */ public function getMediumIntTypeDeclarationSQL(array $column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5511', '%s is deprecated. Use getIntegerTypeDeclarationSQL() instead.', __METHOD__, ); // SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { return 'DATETIME'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { // sqlite autoincrement is only possible for the primary key if (! empty($column['autoincrement'])) { return ' PRIMARY KEY AUTOINCREMENT'; } return ! empty($column['unsigned']) ? ' UNSIGNED' : ''; } /** * Disables schema emulation. * * Schema emulation is enabled by default to maintain backwards compatibility. * Disable it to opt-in to the behavior of DBAL 4. * * @deprecated Will be removed in DBAL 4.0. */ public function disableSchemaEmulation(): void { $this->schemaEmulationEnabled = false; } private function emulateSchemaNamespacing(string $tableName): string { return $this->schemaEmulationEnabled ? str_replace('.', '__', $tableName) : $tableName; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) { return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint( $foreignKey->getQuotedLocalColumns($this), $this->emulateSchemaNamespacing($foreignKey->getQuotedForeignTableName($this)), $foreignKey->getQuotedForeignColumns($this), $foreignKey->getName(), $foreignKey->getOptions(), )); } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $name = $this->emulateSchemaNamespacing($name); $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $constraintName => $definition) { $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition); } } $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options); if (isset($options['foreignKeys'])) { foreach ($options['foreignKeys'] as $foreignKey) { $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey); } } $tableComment = ''; if (isset($options['comment'])) { $comment = trim($options['comment'], " '"); $tableComment = $this->getInlineTableCommentSQL($comment); } $query = ['CREATE TABLE ' . $name . ' ' . $tableComment . '(' . $queryFields . ')']; if (isset($options['alter']) && $options['alter'] === true) { return $query; } if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $indexDef) { $query[] = $this->getCreateIndexSQL($indexDef, $name); } } if (isset($options['unique']) && ! empty($options['unique'])) { foreach ($options['unique'] as $indexDef) { $query[] = $this->getCreateIndexSQL($indexDef, $name); } } return $query; } /** * Generate a PRIMARY KEY definition if no autoincrement value is used * * @param mixed[][] $columns * @param mixed[] $options */ private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options): string { if (empty($options['primary'])) { return ''; } $keyColumns = array_unique(array_values($options['primary'])); foreach ($keyColumns as $keyColumn) { if (! empty($columns[$keyColumn]['autoincrement'])) { return ''; } } return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length > 0 ? 'VARCHAR(' . $length . ')' : 'TEXT'); } /** * {@inheritDoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return 'BLOB'; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryMaxLength() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'SqlitePlatform::getBinaryMaxLength() is deprecated.', ); return 0; } /** * {@inheritDoc} * * @deprecated */ public function getBinaryDefaultLength() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3263', 'Relying on the default binary column length is deprecated, specify the length explicitly.', ); return 0; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'CLOB'; } /** * @deprecated * * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { $table = $this->emulateSchemaNamespacing($table); return sprintf( "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = %s AND sql NOT NULL ORDER BY name", $this->quoteStringLiteral($table), ); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { $table = $this->emulateSchemaNamespacing($table); return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table)); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) { $table = $this->emulateSchemaNamespacing($table); return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table)); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * {@inheritDoc} */ public function getListTablesSQL() { return 'SELECT 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"; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ public function getListViewsSQL($database) { return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey); if (! $foreignKey->hasOption('deferrable') || $foreignKey->getOption('deferrable') === false) { $query .= ' NOT'; } $query .= ' DEFERRABLE'; $query .= ' INITIALLY'; if ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) { $query .= ' DEFERRED'; } else { $query .= ' IMMEDIATE'; } return $query; } /** * {@inheritDoc} * * @deprecated */ public function supportsCreateDropDatabase() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5513', '%s is deprecated.', __METHOD__, ); return false; } /** * {@inheritDoc} */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsColumnCollation() { return true; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function supportsInlineColumnComments() { return true; } /** * {@inheritDoc} */ public function getName() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4749', 'SqlitePlatform::getName() is deprecated. Identify platforms by their class.', ); return 'sqlite'; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); $tableName = $this->emulateSchemaNamespacing($tableIdentifier->getQuotedName($this)); return 'DELETE FROM ' . $tableName; } /** * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction(). * * @deprecated The driver will use {@see sqrt()} in the next major release. * * @param int|float $value * * @return float */ public static function udfSqrt($value) { return sqrt($value); } /** * User-defined function for Sqlite that implements MOD(a, b). * * @deprecated The driver will use {@see UserDefinedFunctions::mod()} in the next major release. * * @param int $a * @param int $b * * @return int */ public static function udfMod($a, $b) { return UserDefinedFunctions::mod($a, $b); } /** * @deprecated The driver will use {@see UserDefinedFunctions::locate()} in the next major release. * * @param string $str * @param string $substr * @param int $offset * * @return int */ public static function udfLocate($str, $substr, $offset = 0) { return UserDefinedFunctions::locate($str, $substr, $offset); } /** * {@inheritDoc} * * @deprecated This API is not portable. */ public function getForUpdateSQL() { return ''; } /** * {@inheritDoc} * * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ public function getInlineColumnCommentSQL($comment) { return '--' . str_replace("\n", "\n--", $comment) . "\n"; } private function getInlineTableCommentSQL(string $comment): string { return $this->getInlineColumnCommentSQL($comment); } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'bigint' => Types\Types::BIGINT, 'bigserial' => Types\Types::BIGINT, 'blob' => Types\Types::BLOB, 'boolean' => Types\Types::BOOLEAN, 'char' => Types\Types::STRING, 'clob' => Types\Types::TEXT, 'date' => Types\Types::DATE_MUTABLE, 'datetime' => Types\Types::DATETIME_MUTABLE, 'decimal' => Types\Types::DECIMAL, 'double' => Types\Types::FLOAT, 'double precision' => Types\Types::FLOAT, 'float' => Types\Types::FLOAT, 'image' => Types\Types::STRING, 'int' => Types\Types::INTEGER, 'integer' => Types\Types::INTEGER, 'longtext' => Types\Types::TEXT, 'longvarchar' => Types\Types::STRING, 'mediumint' => Types\Types::INTEGER, 'mediumtext' => Types\Types::TEXT, 'ntext' => Types\Types::STRING, 'numeric' => Types\Types::DECIMAL, 'nvarchar' => Types\Types::STRING, 'real' => Types\Types::FLOAT, 'serial' => Types\Types::INTEGER, 'smallint' => Types\Types::SMALLINT, 'text' => Types\Types::TEXT, 'time' => Types\Types::TIME_MUTABLE, 'timestamp' => Types\Types::DATETIME_MUTABLE, 'tinyint' => Types\Types::BOOLEAN, 'tinytext' => Types\Types::TEXT, 'varchar' => Types\Types::STRING, 'varchar2' => Types\Types::STRING, ]; } /** * {@inheritDoc} * * @deprecated Implement {@see createReservedKeywordsList()} instead. */ protected function getReservedKeywordsClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4510', 'SqlitePlatform::getReservedKeywordsClass() is deprecated,' . ' use SqlitePlatform::createReservedKeywordsList() instead.', ); return Keywords\SQLiteKeywords::class; } /** * {@inheritDoc} */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { return []; } /** * {@inheritDoc} */ protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) { $table = $diff->getOldTable(); if (! $table instanceof Table) { throw new Exception( 'Sqlite platform requires for alter table the table diff with reference to original table schema', ); } $sql = []; $tableName = $diff->getNewName(); if ($tableName === false) { $tableName = $diff->getName($this); } foreach ($this->getIndexesInAlteredTable($diff, $table) as $index) { if ($index->isPrimary()) { continue; } $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this)); } return $sql; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit === null && $offset > 0) { return sprintf('%s LIMIT -1 OFFSET %d', $query, $offset); } return parent::doModifyLimitQuery($query, $limit, $offset); } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'BLOB'; } /** * {@inheritDoc} */ public function getTemporaryTableName($tableName) { $tableName = $this->emulateSchemaNamespacing($tableName); return $tableName; } /** * {@inheritDoc} * * @deprecated * * Sqlite Platform emulates schema by underscoring each dot and generating tables * into the default database. * * This hack is implemented to be able to use SQLite as testdriver when * using schema supporting databases. */ public function canEmulateSchemas() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4805', 'SqlitePlatform::canEmulateSchemas() is deprecated.', ); return $this->schemaEmulationEnabled; } /** * {@inheritDoc} */ public function getCreateTablesSQL(array $tables): array { $sql = []; foreach ($tables as $table) { $sql = array_merge($sql, $this->getCreateTableSQL($table)); } return $sql; } /** * {@inheritDoc} */ public function getCreateIndexSQL(Index $index, $table) { if ($table instanceof Table) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4798', 'Passing $table as a Table object to %s is deprecated. Pass it as a quoted name instead.', __METHOD__, ); $table = $table->getQuotedName($this); } $name = $index->getQuotedName($this); $columns = $index->getColumns(); if (count($columns) === 0) { throw new InvalidArgumentException(sprintf( 'Incomplete or invalid index definition %s on table %s', $name, $table, )); } if ($index->isPrimary()) { return $this->getCreatePrimaryKeySQL($index, $table); } if (strpos($table, '.') !== false) { [$schema, $table] = explode('.', $table, 2); $name = $schema . '.' . $name; } $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; $query .= ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); return $query; } /** * {@inheritDoc} */ public function getDropTablesSQL(array $tables): array { $sql = []; foreach ($tables as $table) { $sql[] = $this->getDropTableSQL($table->getQuotedName($this)); } return $sql; } /** * {@inheritDoc} */ public function getCreatePrimaryKeySQL(Index $index, $table) { throw new Exception('Sqlite platform does not support alter primary key.'); } /** * {@inheritDoc} */ public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) { throw new Exception('Sqlite platform does not support alter foreign key.'); } /** * {@inheritDoc} */ public function getDropForeignKeySQL($foreignKey, $table) { throw new Exception('Sqlite platform does not support alter foreign key.'); } /** * {@inheritDoc} * * @deprecated */ public function getCreateConstraintSQL(Constraint $constraint, $table) { throw new Exception('Sqlite platform does not support alter constraint.'); } /** * {@inheritDoc} * * @param int|null $createFlags * @psalm-param int-mask-of<AbstractPlatform::CREATE_*>|null $createFlags */ public function getCreateTableSQL(Table $table, $createFlags = null) { $createFlags = $createFlags ?? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS; return parent::getCreateTableSQL($table, $createFlags); } /** * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon. * * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { $table = $this->emulateSchemaNamespacing($table); return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table)); } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = $this->getSimpleAlterTableSQL($diff); if ($sql !== false) { return $sql; } $table = $diff->getOldTable(); if (! $table instanceof Table) { throw new Exception( 'Sqlite platform requires for alter table the table diff with reference to original table schema', ); } $columns = []; $oldColumnNames = []; $newColumnNames = []; $columnSql = []; foreach ($table->getColumns() as $columnName => $column) { $columnName = strtolower($columnName); $columns[$columnName] = $column; $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this); } foreach ($diff->getDroppedColumns() as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $columnName = strtolower($column->getName()); if (! isset($columns[$columnName])) { continue; } unset( $columns[$columnName], $oldColumnNames[$columnName], $newColumnNames[$columnName], ); } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = strtolower($oldColumnName); $columns = $this->replaceColumn( $table->getName(), $columns, $oldColumnName, $column, ); if (! isset($newColumnNames[$oldColumnName])) { continue; } $newColumnNames[$oldColumnName] = $column->getQuotedName($this); } foreach ($diff->getModifiedColumns() as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); $oldColumnName = strtolower($oldColumn->getName()); $columns = $this->replaceColumn( $table->getName(), $columns, $oldColumnName, $columnDiff->getNewColumn(), ); if (! isset($newColumnNames[$oldColumnName])) { continue; } $newColumnNames[$oldColumnName] = $columnDiff->getNewColumn()->getQuotedName($this); } foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columns[strtolower($column->getName())] = $column; } $sql = []; $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { $tableName = $table->getName(); if (strpos($tableName, '.') !== false) { [, $tableName] = explode('.', $tableName, 2); } $dataTable = new Table('__temp__' . $tableName); $newTable = new Table( $table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff, $table), [], $this->getForeignKeysInAlteredTable($diff, $table), $table->getOptions(), ); $newTable->addOption('alter', true); $sql = $this->getPreAlterTableIndexForeignKeySQL($diff); $sql[] = sprintf( 'CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this), ); $sql[] = $this->getDropTableSQL($table); $sql = array_merge($sql, $this->getCreateTableSQL($newTable)); $sql[] = sprintf( 'INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this), ); $sql[] = $this->getDropTableSQL($dataTable->getQuotedName($this)); $newName = $diff->getNewName(); if ($newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of "rename table" SQL using %s is deprecated. Use getRenameTableSQL() instead.', __METHOD__, ); $sql[] = sprintf( 'ALTER TABLE %s RENAME TO %s', $newTable->getQuotedName($this), $newName->getQuotedName($this), ); } $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff)); } return array_merge($sql, $tableSql, $columnSql); } /** * Replace the column with the given name with the new column. * * @param string $tableName * @param array<string,Column> $columns * @param string $columnName * * @return array<string,Column> * * @throws Exception */ private function replaceColumn($tableName, array $columns, $columnName, Column $column): array { $keys = array_keys($columns); $index = array_search($columnName, $keys, true); if ($index === false) { throw SchemaException::columnDoesNotExist($columnName, $tableName); } $values = array_values($columns); $keys[$index] = strtolower($column->getName()); $values[$index] = $column; return array_combine($keys, $values); } /** * @return string[]|false * * @throws Exception */ private function getSimpleAlterTableSQL(TableDiff $diff) { // Suppress changes on integer type autoincrement columns. foreach ($diff->getModifiedColumns() as $columnDiff) { $oldColumn = $columnDiff->getOldColumn(); if ($oldColumn === null) { continue; } $newColumn = $columnDiff->getNewColumn(); if (! $newColumn->getAutoincrement() || ! $newColumn->getType() instanceof IntegerType) { continue; } $oldColumnName = $oldColumn->getName(); if (! $columnDiff->hasTypeChanged() && $columnDiff->hasUnsignedChanged()) { unset($diff->changedColumns[$oldColumnName]); continue; } $fromColumnType = $oldColumn->getType(); if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) { continue; } unset($diff->changedColumns[$oldColumnName]); } if ( count($diff->getModifiedColumns()) > 0 || count($diff->getDroppedColumns()) > 0 || count($diff->getRenamedColumns()) > 0 || count($diff->getAddedIndexes()) > 0 || count($diff->getModifiedIndexes()) > 0 || count($diff->getDroppedIndexes()) > 0 || count($diff->getRenamedIndexes()) > 0 || count($diff->getAddedForeignKeys()) > 0 || count($diff->getModifiedForeignKeys()) > 0 || count($diff->getDroppedForeignKeys()) > 0 ) { return false; } $table = $diff->getOldTable() ?? $diff->getName($this); $sql = []; $tableSql = []; $columnSql = []; foreach ($diff->getAddedColumns() as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $definition = array_merge([ 'unique' => null, 'autoincrement' => null, 'default' => null, ], $column->toArray()); $type = $definition['type']; switch (true) { case isset($definition['columnDefinition']) || $definition['autoincrement'] || $definition['unique']: case $type instanceof Types\DateTimeType && $definition['default'] === $this->getCurrentTimestampSQL(): case $type instanceof Types\DateType && $definition['default'] === $this->getCurrentDateSQL(): case $type instanceof Types\TimeType && $definition['default'] === $this->getCurrentTimeSQL(): return false; } $definition['name'] = $column->getQuotedName($this); if ($type instanceof Types\StringType) { $definition['length'] ??= 255; } $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' . $this->getColumnDeclarationSQL($definition['name'], $definition); } if (! $this->onSchemaAlterTable($diff, $tableSql)) { if ($diff->newName !== false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5663', 'Generation of SQL that renames a table using %s is deprecated.' . ' Use getRenameTableSQL() instead.', __METHOD__, ); $newTable = new Identifier($diff->newName); $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this); } } return array_merge($sql, $tableSql, $columnSql); } /** @return string[] */ private function getColumnNamesInAlteredTable(TableDiff $diff, Table $fromTable): array { $columns = []; foreach ($fromTable->getColumns() as $columnName => $column) { $columns[strtolower($columnName)] = $column->getName(); } foreach ($diff->getDroppedColumns() as $column) { $columnName = strtolower($column->getName()); if (! isset($columns[$columnName])) { continue; } unset($columns[$columnName]); } foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { $columnName = $column->getName(); $columns[strtolower($oldColumnName)] = $columnName; $columns[strtolower($columnName)] = $columnName; } foreach ($diff->getModifiedColumns() as $columnDiff) { $oldColumn = $columnDiff->getOldColumn() ?? $columnDiff->getOldColumnName(); $oldColumnName = $oldColumn->getName(); $newColumnName = $columnDiff->getNewColumn()->getName(); $columns[strtolower($oldColumnName)] = $newColumnName; $columns[strtolower($newColumnName)] = $newColumnName; } foreach ($diff->getAddedColumns() as $column) { $columnName = $column->getName(); $columns[strtolower($columnName)] = $columnName; } return $columns; } /** @return Index[] */ private function getIndexesInAlteredTable(TableDiff $diff, Table $fromTable): array { $indexes = $fromTable->getIndexes(); $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable); foreach ($indexes as $key => $index) { foreach ($diff->getRenamedIndexes() as $oldIndexName => $renamedIndex) { if (strtolower($key) !== strtolower($oldIndexName)) { continue; } unset($indexes[$key]); } $changed = false; $indexColumns = []; foreach ($index->getColumns() as $columnName) { $normalizedColumnName = strtolower($columnName); if (! isset($columnNames[$normalizedColumnName])) { unset($indexes[$key]); continue 2; } $indexColumns[] = $columnNames[$normalizedColumnName]; if ($columnName === $columnNames[$normalizedColumnName]) { continue; } $changed = true; } if (! $changed) { continue; } $indexes[$key] = new Index( $index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags(), ); } foreach ($diff->getDroppedIndexes() as $index) { $indexName = strtolower($index->getName()); if (strlen($indexName) === 0 || ! isset($indexes[$indexName])) { continue; } unset($indexes[$indexName]); } foreach ( array_merge( $diff->getModifiedIndexes(), $diff->getAddedIndexes(), $diff->getRenamedIndexes(), ) as $index ) { $indexName = strtolower($index->getName()); if (strlen($indexName) > 0) { $indexes[$indexName] = $index; } else { $indexes[] = $index; } } return $indexes; } /** @return ForeignKeyConstraint[] */ private function getForeignKeysInAlteredTable(TableDiff $diff, Table $fromTable): array { $foreignKeys = $fromTable->getForeignKeys(); $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable); foreach ($foreignKeys as $key => $constraint) { $changed = false; $localColumns = []; foreach ($constraint->getLocalColumns() as $columnName) { $normalizedColumnName = strtolower($columnName); if (! isset($columnNames[$normalizedColumnName])) { unset($foreignKeys[$key]); continue 2; } $localColumns[] = $columnNames[$normalizedColumnName]; if ($columnName === $columnNames[$normalizedColumnName]) { continue; } $changed = true; } if (! $changed) { continue; } $foreignKeys[$key] = new ForeignKeyConstraint( $localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions(), ); } foreach ($diff->getDroppedForeignKeys() as $constraint) { if (! $constraint instanceof ForeignKeyConstraint) { $constraint = new Identifier($constraint); } $constraintName = strtolower($constraint->getName()); if (strlen($constraintName) === 0 || ! isset($foreignKeys[$constraintName])) { continue; } unset($foreignKeys[$constraintName]); } foreach (array_merge($diff->getModifiedForeignKeys(), $diff->getAddedForeignKeys()) as $constraint) { $constraintName = strtolower($constraint->getName()); if (strlen($constraintName) > 0) { $foreignKeys[$constraintName] = $constraint; } else { $foreignKeys[] = $constraint; } } return $foreignKeys; } /** @return Index[] */ private function getPrimaryIndexInAlteredTable(TableDiff $diff, Table $fromTable): array { $primaryIndex = []; foreach ($this->getIndexesInAlteredTable($diff, $fromTable) as $index) { if (! $index->isPrimary()) { continue; } $primaryIndex = [$index->getName() => $index]; } return $primaryIndex; } public function createSchemaManager(Connection $connection): SqliteSchemaManager { return new SqliteSchemaManager($connection, $this); } } dbal/src/ArrayParameters/Exception.php 0000644 00000000174 15021222234 0014001 0 ustar 00 <?php namespace Doctrine\DBAL\ArrayParameters; use Throwable; /** @internal */ interface Exception extends Throwable { } dbal/src/ArrayParameters/Exception/MissingPositionalParameter.php 0000644 00000000720 15021222234 0021312 0 ustar 00 <?php namespace Doctrine\DBAL\ArrayParameters\Exception; use Doctrine\DBAL\ArrayParameters\Exception; use LogicException; use function sprintf; /** * @internal * * @psalm-immutable */ class MissingPositionalParameter extends LogicException implements Exception { public static function new(int $index): self { return new self( sprintf('Positional parameter at index %d does not have a bound value.', $index), ); } } dbal/src/ArrayParameters/Exception/MissingNamedParameter.php 0000644 00000000654 15021222234 0020223 0 ustar 00 <?php namespace Doctrine\DBAL\ArrayParameters\Exception; use Doctrine\DBAL\ArrayParameters\Exception; use LogicException; use function sprintf; /** @psalm-immutable */ class MissingNamedParameter extends LogicException implements Exception { public static function new(string $name): self { return new self( sprintf('Named parameter "%s" does not have a bound value.', $name), ); } } dbal/src/TransactionIsolationLevel.php 0000644 00000001131 15021222234 0014072 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL; final class TransactionIsolationLevel { /** * Transaction isolation level READ UNCOMMITTED. */ public const READ_UNCOMMITTED = 1; /** * Transaction isolation level READ COMMITTED. */ public const READ_COMMITTED = 2; /** * Transaction isolation level REPEATABLE READ. */ public const REPEATABLE_READ = 3; /** * Transaction isolation level SERIALIZABLE. */ public const SERIALIZABLE = 4; /** @codeCoverageIgnore */ private function __construct() { } } dbal/src/Portability/OptimizeFlags.php 0000644 00000002265 15021222234 0014023 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServerPlatform; final class OptimizeFlags { /** * Platform-specific portability flags that need to be excluded from the user-provided mode * since the platform already operates in this mode to avoid unnecessary conversion overhead. * * @var array<class-string, int> */ private static array $platforms = [ DB2Platform::class => 0, OraclePlatform::class => Connection::PORTABILITY_EMPTY_TO_NULL, PostgreSQLPlatform::class => 0, SqlitePlatform::class => 0, SQLServerPlatform::class => 0, ]; public function __invoke(AbstractPlatform $platform, int $flags): int { foreach (self::$platforms as $class => $mask) { if ($platform instanceof $class) { $flags &= ~$mask; break; } } return $flags; } } dbal/src/Portability/Connection.php 0000644 00000002250 15021222234 0013337 0 ustar 00 <?php namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; use Doctrine\DBAL\Driver\Result as DriverResult; use Doctrine\DBAL\Driver\Statement as DriverStatement; /** * Portability wrapper for a Connection. */ final class Connection extends AbstractConnectionMiddleware { public const PORTABILITY_ALL = 255; public const PORTABILITY_NONE = 0; public const PORTABILITY_RTRIM = 1; public const PORTABILITY_EMPTY_TO_NULL = 4; public const PORTABILITY_FIX_CASE = 8; private Converter $converter; public function __construct(ConnectionInterface $connection, Converter $converter) { parent::__construct($connection); $this->converter = $converter; } public function prepare(string $sql): DriverStatement { return new Statement( parent::prepare($sql), $this->converter, ); } public function query(string $sql): DriverResult { return new Result( parent::query($sql), $this->converter, ); } } dbal/src/Portability/Driver.php 0000644 00000005213 15021222234 0012475 0 ustar 00 <?php namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\ColumnCase; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use LogicException; use PDO; use SensitiveParameter; use function method_exists; final class Driver extends AbstractDriverMiddleware { private int $mode; /** @var 0|ColumnCase::LOWER|ColumnCase::UPPER */ private int $case; /** * @param 0|ColumnCase::LOWER|ColumnCase::UPPER $case Determines how the column case will be treated. * 0: The case will be left as is in the database. * {@see ColumnCase::LOWER}: The case will be lowercased. * {@see ColumnCase::UPPER}: The case will be uppercased. */ public function __construct(DriverInterface $driver, int $mode, int $case) { parent::__construct($driver); $this->mode = $mode; $this->case = $case; } /** * {@inheritDoc} */ public function connect( #[SensitiveParameter] array $params ) { $connection = parent::connect($params); $portability = (new OptimizeFlags())( $this->getDatabasePlatform(), $this->mode, ); $case = null; if ($this->case !== 0 && ($portability & Connection::PORTABILITY_FIX_CASE) !== 0) { $nativeConnection = null; if (method_exists($connection, 'getNativeConnection')) { try { $nativeConnection = $connection->getNativeConnection(); } catch (LogicException $e) { } } if ($nativeConnection instanceof PDO) { $portability &= ~Connection::PORTABILITY_FIX_CASE; $nativeConnection->setAttribute( PDO::ATTR_CASE, $this->case === ColumnCase::LOWER ? PDO::CASE_LOWER : PDO::CASE_UPPER, ); } else { $case = $this->case === ColumnCase::LOWER ? Converter::CASE_LOWER : Converter::CASE_UPPER; } } $convertEmptyStringToNull = ($portability & Connection::PORTABILITY_EMPTY_TO_NULL) !== 0; $rightTrimString = ($portability & Connection::PORTABILITY_RTRIM) !== 0; if (! $convertEmptyStringToNull && ! $rightTrimString && $case === null) { return $connection; } return new Connection( $connection, new Converter($convertEmptyStringToNull, $rightTrimString, $case), ); } } dbal/src/Portability/Result.php 0000644 00000003240 15021222234 0012516 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\Driver\Middleware\AbstractResultMiddleware; use Doctrine\DBAL\Driver\Result as ResultInterface; final class Result extends AbstractResultMiddleware { private Converter $converter; /** @internal The result can be only instantiated by the portability connection or statement. */ public function __construct(ResultInterface $result, Converter $converter) { parent::__construct($result); $this->converter = $converter; } /** * {@inheritDoc} */ public function fetchNumeric() { return $this->converter->convertNumeric( parent::fetchNumeric(), ); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->converter->convertAssociative( parent::fetchAssociative(), ); } /** * {@inheritDoc} */ public function fetchOne() { return $this->converter->convertOne( parent::fetchOne(), ); } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { return $this->converter->convertAllNumeric( parent::fetchAllNumeric(), ); } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { return $this->converter->convertAllAssociative( parent::fetchAllAssociative(), ); } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { return $this->converter->convertFirstColumn( parent::fetchFirstColumn(), ); } } dbal/src/Portability/Middleware.php 0000644 00000002207 15021222234 0013317 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\ColumnCase; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface; final class Middleware implements MiddlewareInterface { private int $mode; /** @var 0|ColumnCase::LOWER|ColumnCase::UPPER */ private int $case; /** * @param 0|ColumnCase::LOWER|ColumnCase::UPPER $case Determines how the column case will be treated. * 0: The case will be left as is in the database. * {@see ColumnCase::LOWER}: The case will be lowercased. * {@see ColumnCase::UPPER}: The case will be uppercased. */ public function __construct(int $mode, int $case) { $this->mode = $mode; $this->case = $case; } public function wrap(DriverInterface $driver): DriverInterface { if ($this->mode !== 0) { return new Driver($driver, $this->mode, $this->case); } return $driver; } } dbal/src/Portability/Converter.php 0000644 00000020641 15021222234 0013213 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Portability; use function array_change_key_case; use function array_map; use function array_reduce; use function is_string; use function rtrim; use const CASE_LOWER; use const CASE_UPPER; final class Converter { public const CASE_LOWER = CASE_LOWER; public const CASE_UPPER = CASE_UPPER; /** @var callable */ private $convertNumeric; /** @var callable */ private $convertAssociative; /** @var callable */ private $convertOne; /** @var callable */ private $convertAllNumeric; /** @var callable */ private $convertAllAssociative; /** @var callable */ private $convertFirstColumn; /** * @param bool $convertEmptyStringToNull Whether each empty string should * be converted to NULL * @param bool $rightTrimString Whether each string should right-trimmed * @param self::CASE_LOWER|self::CASE_UPPER|null $case Convert the case of the column names * (one of {@see self::CASE_LOWER} and * {@see self::CASE_UPPER}) */ public function __construct(bool $convertEmptyStringToNull, bool $rightTrimString, ?int $case) { $convertValue = $this->createConvertValue($convertEmptyStringToNull, $rightTrimString); $convertNumeric = $this->createConvertRow($convertValue, null); $convertAssociative = $this->createConvertRow($convertValue, $case); $this->convertNumeric = $this->createConvert($convertNumeric, [self::class, 'id']); $this->convertAssociative = $this->createConvert($convertAssociative, [self::class, 'id']); $this->convertOne = $this->createConvert($convertValue, [self::class, 'id']); $this->convertAllNumeric = $this->createConvertAll($convertNumeric, [self::class, 'id']); $this->convertAllAssociative = $this->createConvertAll($convertAssociative, [self::class, 'id']); $this->convertFirstColumn = $this->createConvertAll($convertValue, [self::class, 'id']); } /** * @param array<int,mixed>|false $row * * @return list<mixed>|false */ public function convertNumeric($row) { return ($this->convertNumeric)($row); } /** * @param array<string,mixed>|false $row * * @return array<string,mixed>|false */ public function convertAssociative($row) { return ($this->convertAssociative)($row); } /** * @param mixed|false $value * * @return mixed|false */ public function convertOne($value) { return ($this->convertOne)($value); } /** * @param list<list<mixed>> $data * * @return list<list<mixed>> */ public function convertAllNumeric(array $data): array { return ($this->convertAllNumeric)($data); } /** * @param list<array<string,mixed>> $data * * @return list<array<string,mixed>> */ public function convertAllAssociative(array $data): array { return ($this->convertAllAssociative)($data); } /** * @param list<mixed> $data * * @return list<mixed> */ public function convertFirstColumn(array $data): array { return ($this->convertFirstColumn)($data); } /** * @param T $value * * @return T * * @template T */ private static function id($value) { return $value; } /** * @param T $value * * @return T|null * * @template T */ private static function convertEmptyStringToNull($value) { if ($value === '') { return null; } return $value; } /** * @param T $value * * @return T|string * @psalm-return (T is string ? string : T) * * @template T */ private static function rightTrimString($value) { if (! is_string($value)) { return $value; } return rtrim($value); } /** * Creates a function that will convert each individual value retrieved from the database * * @param bool $convertEmptyStringToNull Whether each empty string should be converted to NULL * @param bool $rightTrimString Whether each string should right-trimmed * * @return callable|null The resulting function or NULL if no conversion is needed */ private function createConvertValue(bool $convertEmptyStringToNull, bool $rightTrimString): ?callable { $functions = []; if ($convertEmptyStringToNull) { $functions[] = [self::class, 'convertEmptyStringToNull']; } if ($rightTrimString) { $functions[] = [self::class, 'rightTrimString']; } return $this->compose(...$functions); } /** * Creates a function that will convert each array-row retrieved from the database * * @param callable|null $function The function that will convert each value * @param self::CASE_LOWER|self::CASE_UPPER|null $case Column name case * * @return callable|null The resulting function or NULL if no conversion is needed */ private function createConvertRow(?callable $function, ?int $case): ?callable { $functions = []; if ($function !== null) { $functions[] = $this->createMapper($function); } if ($case !== null) { $functions[] = static function (array $row) use ($case): array { return array_change_key_case($row, $case); }; } return $this->compose(...$functions); } /** * Creates a function that will be applied to the return value of Statement::fetch*() * or an identity function if no conversion is needed * * @param callable|null $function The function that will convert each tow * @param callable $id Identity function */ private function createConvert(?callable $function, callable $id): callable { if ($function === null) { return $id; } return /** * @param T $value * * @psalm-return (T is false ? false : T) * * @template T */ static function ($value) use ($function) { if ($value === false) { return false; } return $function($value); }; } /** * Creates a function that will be applied to the return value of Statement::fetchAll*() * or an identity function if no transformation is required * * @param callable|null $function The function that will transform each value * @param callable $id Identity function */ private function createConvertAll(?callable $function, callable $id): callable { if ($function === null) { return $id; } return $this->createMapper($function); } /** * Creates a function that maps each value of the array using the given function * * @param callable $function The function that maps each value of the array */ private function createMapper(callable $function): callable { return static function (array $array) use ($function): array { return array_map($function, $array); }; } /** * Creates a composition of the given set of functions * * @param callable(T):T ...$functions The functions to compose * * @return callable(T):T|null * * @template T */ private function compose(callable ...$functions): ?callable { return array_reduce($functions, static function (?callable $carry, callable $item): callable { if ($carry === null) { return $item; } return /** * @param T $value * * @return T * * @template T */ static function ($value) use ($carry, $item) { return $item($carry($value)); }; }); } } dbal/src/Portability/Statement.php 0000644 00000001473 15021222234 0013212 0 ustar 00 <?php namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as DriverStatement; /** * Portability wrapper for a Statement. */ final class Statement extends AbstractStatementMiddleware { private Converter $converter; /** * Wraps <tt>Statement</tt> and applies portability measures. */ public function __construct(DriverStatement $stmt, Converter $converter) { parent::__construct($stmt); $this->converter = $converter; } /** * {@inheritDoc} */ public function execute($params = null): ResultInterface { return new Result( parent::execute($params), $this->converter, ); } } dbal/src/Query/Limit.php 0000644 00000001114 15021222234 0011117 0 ustar 00 <?php namespace Doctrine\DBAL\Query; final class Limit { private ?int $maxResults; private int $firstResult; public function __construct(?int $maxResults, int $firstResult) { $this->maxResults = $maxResults; $this->firstResult = $firstResult; } public function isDefined(): bool { return $this->maxResults !== null || $this->firstResult !== 0; } public function getMaxResults(): ?int { return $this->maxResults; } public function getFirstResult(): int { return $this->firstResult; } } dbal/src/Query/SelectQuery.php 0000644 00000004106 15021222234 0012312 0 ustar 00 <?php namespace Doctrine\DBAL\Query; final class SelectQuery { private bool $distinct; /** @var string[] */ private array $columns; /** @var string[] */ private array $from; private ?string $where; /** @var string[] */ private array $groupBy; private ?string $having; /** @var string[] */ private array $orderBy; private Limit $limit; private ?ForUpdate $forUpdate; /** * @internal This class should be instantiated only by {@link QueryBuilder}. * * @param string[] $columns * @param string[] $from * @param string[] $groupBy * @param string[] $orderBy */ public function __construct( bool $distinct, array $columns, array $from, ?string $where, array $groupBy, ?string $having, array $orderBy, Limit $limit, ?ForUpdate $forUpdate ) { $this->distinct = $distinct; $this->columns = $columns; $this->from = $from; $this->where = $where; $this->groupBy = $groupBy; $this->having = $having; $this->orderBy = $orderBy; $this->limit = $limit; $this->forUpdate = $forUpdate; } public function isDistinct(): bool { return $this->distinct; } /** @return string[] */ public function getColumns(): array { return $this->columns; } /** @return string[] */ public function getFrom(): array { return $this->from; } public function getWhere(): ?string { return $this->where; } /** @return string[] */ public function getGroupBy(): array { return $this->groupBy; } public function getHaving(): ?string { return $this->having; } /** @return string[] */ public function getOrderBy(): array { return $this->orderBy; } public function getLimit(): Limit { return $this->limit; } public function getForUpdate(): ?ForUpdate { return $this->forUpdate; } } dbal/src/Query/ForUpdate/ConflictResolutionMode.php 0000644 00000000653 15021222234 0016373 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Query\ForUpdate; final class ConflictResolutionMode { /** * Wait for the row to be unlocked */ public const ORDINARY = 0; /** * Skip the row if it is locked */ public const SKIP_LOCKED = 1; /** * This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } } dbal/src/Query/QueryException.php 0000644 00000001767 15021222234 0013043 0 ustar 00 <?php namespace Doctrine\DBAL\Query; use Doctrine\DBAL\Exception; use function implode; /** @psalm-immutable */ class QueryException extends Exception { /** * @param string $alias * @param string[] $registeredAliases * * @return QueryException */ public static function unknownAlias($alias, $registeredAliases) { return new self("The given alias '" . $alias . "' is not part of " . 'any FROM or JOIN clause table. The currently registered ' . 'aliases are: ' . implode(', ', $registeredAliases) . '.'); } /** * @param string $alias * @param string[] $registeredAliases * * @return QueryException */ public static function nonUniqueAlias($alias, $registeredAliases) { return new self("The given alias '" . $alias . "' is not unique " . 'in FROM and JOIN clause table. The currently registered ' . 'aliases are: ' . implode(', ', $registeredAliases) . '.'); } } dbal/src/Query/QueryBuilder.php 0000644 00000146212 15021222234 0012466 0 ustar 00 <?php namespace Doctrine\DBAL\Query; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\Expression\CompositeExpression; use Doctrine\DBAL\Query\Expression\ExpressionBuilder; use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode; use Doctrine\DBAL\Result; use Doctrine\DBAL\Statement; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_key_exists; use function array_keys; use function array_unshift; use function count; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function is_array; use function is_object; use function key; use function method_exists; use function strtoupper; use function substr; use function ucfirst; /** * QueryBuilder class is responsible to dynamically create SQL queries. * * Important: Verify that every feature you use will work with your database vendor. * SQL Query Builder does not attempt to validate the generated SQL at all. * * The query builder does no validation whatsoever if certain features even work with the * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements * even if some vendors such as MySQL support it. * * @method $this distinct(bool $distinct = true) Adds or removes DISTINCT to/from the query. */ class QueryBuilder { /** @deprecated */ public const SELECT = 0; /** @deprecated */ public const DELETE = 1; /** @deprecated */ public const UPDATE = 2; /** @deprecated */ public const INSERT = 3; /** @deprecated */ public const STATE_DIRTY = 0; /** @deprecated */ public const STATE_CLEAN = 1; /** * The DBAL Connection. */ private Connection $connection; /* * The default values of SQL parts collection */ private const SQL_PARTS_DEFAULTS = [ 'select' => [], 'distinct' => false, 'from' => [], 'join' => [], 'set' => [], 'where' => null, 'groupBy' => [], 'having' => null, 'orderBy' => [], 'values' => [], 'for_update' => null, ]; /** * The array of SQL parts collected. * * @var mixed[] */ private array $sqlParts = self::SQL_PARTS_DEFAULTS; /** * The complete SQL string for this query. */ private ?string $sql = null; /** * The query parameters. * * @var list<mixed>|array<string, mixed> */ private $params = []; /** * The parameter type map of this query. * * @var array<int, int|string|Type|null>|array<string, int|string|Type|null> */ private array $paramTypes = []; /** * The type of query this is. Can be select, update or delete. * * @psalm-var self::SELECT|self::DELETE|self::UPDATE|self::INSERT */ private int $type = self::SELECT; /** * The state of the query object. Can be dirty or clean. * * @psalm-var self::STATE_* */ private int $state = self::STATE_CLEAN; /** * The index of the first result to retrieve. */ private int $firstResult = 0; /** * The maximum number of results to retrieve or NULL to retrieve all results. */ private ?int $maxResults = null; /** * The counter of bound parameters used with {@see bindValue). */ private int $boundCounter = 0; /** * The query cache profile used for caching results. */ private ?QueryCacheProfile $resultCacheProfile = null; /** * Initializes a new <tt>QueryBuilder</tt>. * * @param Connection $connection The DBAL Connection. */ public function __construct(Connection $connection) { $this->connection = $connection; } /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. * This producer method is intended for convenient inline usage. Example: * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where($qb->expr()->eq('u.id', 1)); * </code> * * For more complex expression construction, consider storing the expression * builder object in a local variable. * * @return ExpressionBuilder */ public function expr() { return $this->connection->getExpressionBuilder(); } /** * Gets the type of the currently built query. * * @deprecated If necessary, track the type of the query being built outside of the builder. * * @return int */ public function getType() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5551', 'Relying on the type of the query being built is deprecated.' . ' If necessary, track the type of the query being built outside of the builder.', ); return $this->type; } /** * Gets the associated DBAL Connection for this query builder. * * @deprecated Use the connection used to instantiate the builder instead. * * @return Connection */ public function getConnection() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5780', '%s is deprecated. Use the connection used to instantiate the builder instead.', __METHOD__, ); return $this->connection; } /** * Gets the state of this query builder instance. * * @deprecated The builder state is an internal concern. * * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. * @psalm-return self::STATE_* */ public function getState() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5551', 'Relying on the query builder state is deprecated as it is an internal concern.', ); return $this->state; } /** * Prepares and executes an SQL query and returns the first row of the result * as an associative array. * * @return array<string, mixed>|false False is returned if no rows are found. * * @throws Exception */ public function fetchAssociative() { return $this->executeQuery()->fetchAssociative(); } /** * Prepares and executes an SQL query and returns the first row of the result * as a numerically indexed array. * * @return array<int, mixed>|false False is returned if no rows are found. * * @throws Exception */ public function fetchNumeric() { return $this->executeQuery()->fetchNumeric(); } /** * Prepares and executes an SQL query and returns the value of a single column * of the first row of the result. * * @return mixed|false False is returned if no rows are found. * * @throws Exception */ public function fetchOne() { return $this->executeQuery()->fetchOne(); } /** * Prepares and executes an SQL query and returns the result as an array of numeric arrays. * * @return array<int,array<int,mixed>> * * @throws Exception */ public function fetchAllNumeric(): array { return $this->executeQuery()->fetchAllNumeric(); } /** * Prepares and executes an SQL query and returns the result as an array of associative arrays. * * @return array<int,array<string,mixed>> * * @throws Exception */ public function fetchAllAssociative(): array { return $this->executeQuery()->fetchAllAssociative(); } /** * Prepares and executes an SQL query and returns the result as an associative array with the keys * mapped to the first column and the values mapped to the second column. * * @return array<mixed,mixed> * * @throws Exception */ public function fetchAllKeyValue(): array { return $this->executeQuery()->fetchAllKeyValue(); } /** * Prepares and executes an SQL query and returns the result as 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 { return $this->executeQuery()->fetchAllAssociativeIndexed(); } /** * Prepares and executes an SQL query and returns the result as an array of the first column values. * * @return array<int,mixed> * * @throws Exception */ public function fetchFirstColumn(): array { return $this->executeQuery()->fetchFirstColumn(); } /** * Executes an SQL query (SELECT) and returns a Result. * * @throws Exception */ public function executeQuery(): Result { return $this->connection->executeQuery( $this->getSQL(), $this->params, $this->paramTypes, $this->resultCacheProfile, ); } /** * Executes an SQL statement and returns the number of affected rows. * * Should be used for INSERT, UPDATE and DELETE * * @return int The number of affected rows. * * @throws Exception */ public function executeStatement(): int { return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes); } /** * Executes this query using the bound parameters and their types. * * @deprecated Use {@see executeQuery()} or {@see executeStatement()} instead. * * @return Result|int|string * * @throws Exception */ public function execute() { if ($this->type === self::SELECT) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4578', 'QueryBuilder::execute() is deprecated, use QueryBuilder::executeQuery() for SQL queries instead.', ); return $this->executeQuery(); } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4578', 'QueryBuilder::execute() is deprecated, use QueryBuilder::executeStatement() for SQL statements instead.', ); return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes); } /** * Gets the complete SQL string formed by the current specifications of this QueryBuilder. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * echo $qb->getSQL(); // SELECT u FROM User u * </code> * * @return string The SQL query string. */ public function getSQL() { if ($this->sql !== null && $this->state === self::STATE_CLEAN) { return $this->sql; } switch ($this->type) { case self::INSERT: $sql = $this->getSQLForInsert(); break; case self::DELETE: $sql = $this->getSQLForDelete(); break; case self::UPDATE: $sql = $this->getSQLForUpdate(); break; case self::SELECT: $sql = $this->getSQLForSelect(); break; } $this->state = self::STATE_CLEAN; $this->sql = $sql; return $sql; } /** * Sets a query parameter for the query being constructed. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where('u.id = :user_id') * ->setParameter('user_id', 1); * </code> * * @param int|string $key Parameter position or name * @param mixed $value Parameter value * @param int|string|Type|null $type Parameter type * * @return $this This QueryBuilder instance. */ public function setParameter($key, $value, $type = ParameterType::STRING) { if ($type !== null) { $this->paramTypes[$key] = $type; } else { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5550', 'Using NULL as prepared statement parameter type is deprecated.' . 'Omit or use ParameterType::STRING instead', ); } $this->params[$key] = $value; return $this; } /** * Sets a collection of query parameters for the query being constructed. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where('u.id = :user_id1 OR u.id = :user_id2') * ->setParameters(array( * 'user_id1' => 1, * 'user_id2' => 2 * )); * </code> * * @param list<mixed>|array<string, mixed> $params Parameters to set * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return $this This QueryBuilder instance. */ public function setParameters(array $params, array $types = []) { $this->paramTypes = $types; $this->params = $params; return $this; } /** * Gets all defined query parameters for the query being constructed indexed by parameter index or name. * * @return list<mixed>|array<string, mixed> The currently defined query parameters */ public function getParameters() { return $this->params; } /** * Gets a (previously set) query parameter of the query being constructed. * * @param mixed $key The key (index or name) of the bound parameter. * * @return mixed The value of the bound parameter. */ public function getParameter($key) { return $this->params[$key] ?? null; } /** * Gets all defined query parameter types for the query being constructed indexed by parameter index or name. * * @return array<int, int|string|Type|null>|array<string, int|string|Type|null> The currently defined * query parameter types */ public function getParameterTypes() { return $this->paramTypes; } /** * Gets a (previously set) query parameter type of the query being constructed. * * @param int|string $key The key of the bound parameter type * * @return int|string|Type The value of the bound parameter type */ public function getParameterType($key) { return $this->paramTypes[$key] ?? ParameterType::STRING; } /** * Sets the position of the first result to retrieve (the "offset"). * * @param int $firstResult The first result to return. * * @return $this This QueryBuilder instance. */ public function setFirstResult($firstResult) { $this->state = self::STATE_DIRTY; $this->firstResult = $firstResult; return $this; } /** * Gets the position of the first result the query object was set to retrieve (the "offset"). * * @return int The position of the first result. */ public function getFirstResult() { return $this->firstResult; } /** * Sets the maximum number of results to retrieve (the "limit"). * * @param int|null $maxResults The maximum number of results to retrieve or NULL to retrieve all results. * * @return $this This QueryBuilder instance. */ public function setMaxResults($maxResults) { $this->state = self::STATE_DIRTY; $this->maxResults = $maxResults; return $this; } /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if all results will be returned. * * @return int|null The maximum number of results. */ public function getMaxResults() { return $this->maxResults; } /** * Locks the queried rows for a subsequent update. * * @return $this */ public function forUpdate(int $conflictResolutionMode = ConflictResolutionMode::ORDINARY): self { $this->state = self::STATE_DIRTY; $this->sqlParts['for_update'] = new ForUpdate($conflictResolutionMode); return $this; } /** * Either appends to or replaces a single, generic query part. * * The available parts are: 'select', 'from', 'set', 'where', * 'groupBy', 'having' and 'orderBy'. * * @param string $sqlPartName * @param mixed $sqlPart * @param bool $append * * @return $this This QueryBuilder instance. */ public function add($sqlPartName, $sqlPart, $append = false) { $isArray = is_array($sqlPart); $isMultiple = is_array($this->sqlParts[$sqlPartName]); if ($isMultiple && ! $isArray) { $sqlPart = [$sqlPart]; } $this->state = self::STATE_DIRTY; if ($append) { if ( $sqlPartName === 'orderBy' || $sqlPartName === 'groupBy' || $sqlPartName === 'select' || $sqlPartName === 'set' ) { foreach ($sqlPart as $part) { $this->sqlParts[$sqlPartName][] = $part; } } elseif ($isArray && is_array($sqlPart[key($sqlPart)])) { $key = key($sqlPart); $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key]; } elseif ($isMultiple) { $this->sqlParts[$sqlPartName][] = $sqlPart; } else { $this->sqlParts[$sqlPartName] = $sqlPart; } return $this; } $this->sqlParts[$sqlPartName] = $sqlPart; return $this; } /** * Specifies an item that is to be returned in the query result. * Replaces any previously specified selections, if any. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id', 'p.id') * ->from('users', 'u') * ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id'); * </code> * * @param string|string[]|null $select The selection expression. USING AN ARRAY OR NULL IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function select($select = null/*, string ...$selects*/) { $this->type = self::SELECT; if ($select === null) { return $this; } if (is_array($select)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::select() is deprecated, ' . 'pass each value as an individual variadic argument instead.', ); } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', $selects); } /** * Adds or removes DISTINCT to/from the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id') * ->distinct() * ->from('users', 'u') * </code> * * @return $this This QueryBuilder instance. */ public function distinct(/* bool $distinct = true */): self { $this->sqlParts['distinct'] = func_num_args() < 1 || func_get_arg(0); $this->state = self::STATE_DIRTY; return $this; } /** * Adds an item that is to be returned in the query result. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id') * ->addSelect('p.id') * ->from('users', 'u') * ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id'); * </code> * * @param string|string[]|null $select The selection expression. USING AN ARRAY OR NULL IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function addSelect($select = null/*, string ...$selects*/) { $this->type = self::SELECT; if ($select === null) { return $this; } if (is_array($select)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::addSelect() is deprecated, ' . 'pass each value as an individual variadic argument instead.', ); } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', $selects, true); } /** * Turns the query being built into a bulk delete query that ranges over * a certain table. * * <code> * $qb = $conn->createQueryBuilder() * ->delete('users', 'u') * ->where('u.id = :user_id') * ->setParameter(':user_id', 1); * </code> * * @param string $delete The table whose rows are subject to the deletion. * @param string $alias The table alias used in the constructed query. * * @return $this This QueryBuilder instance. */ public function delete($delete = null, $alias = null) { $this->type = self::DELETE; if ($delete === null) { return $this; } return $this->add('from', [ 'table' => $delete, 'alias' => $alias, ]); } /** * Turns the query being built into a bulk update query that ranges over * a certain table * * <code> * $qb = $conn->createQueryBuilder() * ->update('counters', 'c') * ->set('c.value', 'c.value + 1') * ->where('c.id = ?'); * </code> * * @param string $update The table whose rows are subject to the update. * @param string $alias The table alias used in the constructed query. * * @return $this This QueryBuilder instance. */ public function update($update = null, $alias = null) { $this->type = self::UPDATE; if ($update === null) { return $this; } return $this->add('from', [ 'table' => $update, 'alias' => $alias, ]); } /** * Turns the query being built into an insert query that inserts into * a certain table * * <code> * $qb = $conn->createQueryBuilder() * ->insert('users') * ->values( * array( * 'name' => '?', * 'password' => '?' * ) * ); * </code> * * @param string $insert The table into which the rows should be inserted. * * @return $this This QueryBuilder instance. */ public function insert($insert = null) { $this->type = self::INSERT; if ($insert === null) { return $this; } return $this->add('from', ['table' => $insert]); } /** * Creates and adds a query root corresponding to the table identified by the * given alias, forming a cartesian product with any existing query roots. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id') * ->from('users', 'u') * </code> * * @param string $from The table. * @param string|null $alias The alias of the table. * * @return $this This QueryBuilder instance. */ public function from($from, $alias = null) { return $this->add('from', [ 'table' => $from, 'alias' => $alias, ], true); } /** * Creates and adds a join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function join($fromAlias, $join, $alias, $condition = null) { return $this->innerJoin($fromAlias, $join, $alias, $condition); } /** * Creates and adds a join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function innerJoin($fromAlias, $join, $alias, $condition = null) { return $this->add('join', [ $fromAlias => [ 'joinType' => 'inner', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition, ], ], true); } /** * Creates and adds a left join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function leftJoin($fromAlias, $join, $alias, $condition = null) { return $this->add('join', [ $fromAlias => [ 'joinType' => 'left', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition, ], ], true); } /** * Creates and adds a right join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function rightJoin($fromAlias, $join, $alias, $condition = null) { return $this->add('join', [ $fromAlias => [ 'joinType' => 'right', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition, ], ], true); } /** * Sets a new value for a column in a bulk update query. * * <code> * $qb = $conn->createQueryBuilder() * ->update('counters', 'c') * ->set('c.value', 'c.value + 1') * ->where('c.id = ?'); * </code> * * @param string $key The column to set. * @param string $value The value, expression, placeholder, etc. * * @return $this This QueryBuilder instance. */ public function set($key, $value) { return $this->add('set', $key . ' = ' . $value, true); } /** * Specifies one or more restrictions to the query result. * Replaces any previously specified restrictions, if any. * * <code> * $qb = $conn->createQueryBuilder() * ->select('c.value') * ->from('counters', 'c') * ->where('c.id = ?'); * * // You can optionally programmatically build and/or expressions * $qb = $conn->createQueryBuilder(); * * $or = $qb->expr()->orx(); * $or->add($qb->expr()->eq('c.id', 1)); * $or->add($qb->expr()->eq('c.id', 2)); * * $qb->update('counters', 'c') * ->set('c.value', 'c.value + 1') * ->where($or); * </code> * * @param mixed $predicates The restriction predicates. * * @return $this This QueryBuilder instance. */ public function where($predicates) { if (! (func_num_args() === 1 && $predicates instanceof CompositeExpression)) { $predicates = CompositeExpression::and(...func_get_args()); } return $this->add('where', $predicates); } /** * Adds one or more restrictions to the query results, forming a logical * conjunction with any previously specified restrictions. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where('u.username LIKE ?') * ->andWhere('u.is_active = 1'); * </code> * * @see where() * * @param mixed $where The query restrictions. * * @return $this This QueryBuilder instance. */ public function andWhere($where) { $args = func_get_args(); $where = $this->getQueryPart('where'); if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) { $where = $where->with(...$args); } else { array_unshift($args, $where); $where = CompositeExpression::and(...$args); } return $this->add('where', $where, true); } /** * Adds one or more restrictions to the query results, forming a logical * disjunction with any previously specified restrictions. * * <code> * $qb = $em->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->where('u.id = 1') * ->orWhere('u.id = 2'); * </code> * * @see where() * * @param mixed $where The WHERE statement. * * @return $this This QueryBuilder instance. */ public function orWhere($where) { $args = func_get_args(); $where = $this->getQueryPart('where'); if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) { $where = $where->with(...$args); } else { array_unshift($args, $where); $where = CompositeExpression::or(...$args); } return $this->add('where', $where, true); } /** * Specifies a grouping over the results of the query. * Replaces any previously specified groupings, if any. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->groupBy('u.id'); * </code> * * @param string|string[] $groupBy The grouping expression. USING AN ARRAY IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function groupBy($groupBy/*, string ...$groupBys*/) { if (is_array($groupBy) && count($groupBy) === 0) { return $this; } if (is_array($groupBy)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::groupBy() is deprecated, ' . 'pass each value as an individual variadic argument instead.', ); } $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); return $this->add('groupBy', $groupBy, false); } /** * Adds a grouping expression to the query. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->groupBy('u.lastLogin') * ->addGroupBy('u.createdAt'); * </code> * * @param string|string[] $groupBy The grouping expression. USING AN ARRAY IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function addGroupBy($groupBy/*, string ...$groupBys*/) { if (is_array($groupBy) && count($groupBy) === 0) { return $this; } if (is_array($groupBy)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::addGroupBy() is deprecated, ' . 'pass each value as an individual variadic argument instead.', ); } $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); return $this->add('groupBy', $groupBy, true); } /** * Sets a value for a column in an insert query. * * <code> * $qb = $conn->createQueryBuilder() * ->insert('users') * ->values( * array( * 'name' => '?' * ) * ) * ->setValue('password', '?'); * </code> * * @param string $column The column into which the value should be inserted. * @param string $value The value that should be inserted into the column. * * @return $this This QueryBuilder instance. */ public function setValue($column, $value) { $this->sqlParts['values'][$column] = $value; return $this; } /** * Specifies values for an insert query indexed by column names. * Replaces any previous values, if any. * * <code> * $qb = $conn->createQueryBuilder() * ->insert('users') * ->values( * array( * 'name' => '?', * 'password' => '?' * ) * ); * </code> * * @param mixed[] $values The values to specify for the insert query indexed by column names. * * @return $this This QueryBuilder instance. */ public function values(array $values) { return $this->add('values', $values); } /** * Specifies a restriction over the groups of the query. * Replaces any previous having restrictions, if any. * * @param mixed $having The restriction over the groups. * * @return $this This QueryBuilder instance. */ public function having($having) { if (! (func_num_args() === 1 && $having instanceof CompositeExpression)) { $having = CompositeExpression::and(...func_get_args()); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * conjunction with any existing having restrictions. * * @param mixed $having The restriction to append. * * @return $this This QueryBuilder instance. */ public function andHaving($having) { $args = func_get_args(); $having = $this->getQueryPart('having'); if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) { $having = $having->with(...$args); } else { array_unshift($args, $having); $having = CompositeExpression::and(...$args); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * disjunction with any existing having restrictions. * * @param mixed $having The restriction to add. * * @return $this This QueryBuilder instance. */ public function orHaving($having) { $args = func_get_args(); $having = $this->getQueryPart('having'); if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) { $having = $having->with(...$args); } else { array_unshift($args, $having); $having = CompositeExpression::or(...$args); } return $this->add('having', $having); } /** * Specifies an ordering for the query results. * Replaces any previously specified orderings, if any. * * @param string $sort The ordering expression. * @param string $order The ordering direction. * * @return $this This QueryBuilder instance. */ public function orderBy($sort, $order = null) { return $this->add('orderBy', $sort . ' ' . ($order ?? 'ASC'), false); } /** * Adds an ordering to the query results. * * @param string $sort The ordering expression. * @param string $order The ordering direction. * * @return $this This QueryBuilder instance. */ public function addOrderBy($sort, $order = null) { return $this->add('orderBy', $sort . ' ' . ($order ?? 'ASC'), true); } /** * Gets a query part by its name. * * @deprecated The query parts are implementation details and should not be relied upon. * * @param string $queryPartName * * @return mixed */ public function getQueryPart($queryPartName) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6179', 'Getting query parts is deprecated as they are implementation details.', ); return $this->sqlParts[$queryPartName]; } /** * Gets all query parts. * * @deprecated The query parts are implementation details and should not be relied upon. * * @return mixed[] */ public function getQueryParts() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6179', 'Getting query parts is deprecated as they are implementation details.', ); return $this->sqlParts; } /** * Resets SQL parts. * * @deprecated Use the dedicated reset*() methods instead. * * @param string[]|null $queryPartNames * * @return $this This QueryBuilder instance. */ public function resetQueryParts($queryPartNames = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6193', '%s() is deprecated, instead use dedicated reset methods for the parts that shall be reset.', __METHOD__, ); $queryPartNames ??= array_keys($this->sqlParts); foreach ($queryPartNames as $queryPartName) { $this->sqlParts[$queryPartName] = self::SQL_PARTS_DEFAULTS[$queryPartName]; } $this->state = self::STATE_DIRTY; return $this; } /** * Resets a single SQL part. * * @deprecated Use the dedicated reset*() methods instead. * * @param string $queryPartName * * @return $this This QueryBuilder instance. */ public function resetQueryPart($queryPartName) { if ($queryPartName === 'distinct') { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6193', 'Calling %s() with "distinct" is deprecated, call distinct(false) instead.', __METHOD__, ); return $this->distinct(false); } $newMethodName = 'reset' . ucfirst($queryPartName); if (array_key_exists($queryPartName, self::SQL_PARTS_DEFAULTS) && method_exists($this, $newMethodName)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6193', 'Calling %s() with "%s" is deprecated, call %s() instead.', __METHOD__, $queryPartName, $newMethodName, ); return $this->$newMethodName(); } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/6193', 'Calling %s() with "%s" is deprecated without replacement.', __METHOD__, $queryPartName, $newMethodName, ); $this->sqlParts[$queryPartName] = self::SQL_PARTS_DEFAULTS[$queryPartName]; $this->state = self::STATE_DIRTY; return $this; } /** * Resets the WHERE conditions for the query. * * @return $this This QueryBuilder instance. */ public function resetWhere(): self { $this->sqlParts['where'] = self::SQL_PARTS_DEFAULTS['where']; $this->state = self::STATE_DIRTY; return $this; } /** * Resets the grouping for the query. * * @return $this This QueryBuilder instance. */ public function resetGroupBy(): self { $this->sqlParts['groupBy'] = self::SQL_PARTS_DEFAULTS['groupBy']; $this->state = self::STATE_DIRTY; return $this; } /** * Resets the HAVING conditions for the query. * * @return $this This QueryBuilder instance. */ public function resetHaving(): self { $this->sqlParts['having'] = self::SQL_PARTS_DEFAULTS['having']; $this->state = self::STATE_DIRTY; return $this; } /** * Resets the ordering for the query. * * @return $this This QueryBuilder instance. */ public function resetOrderBy(): self { $this->sqlParts['orderBy'] = self::SQL_PARTS_DEFAULTS['orderBy']; $this->state = self::STATE_DIRTY; return $this; } /** @throws Exception */ private function getSQLForSelect(): string { return $this->connection->getDatabasePlatform() ->createSelectSQLBuilder() ->buildSQL( new SelectQuery( $this->sqlParts['distinct'], $this->sqlParts['select'], $this->getFromClauses(), $this->sqlParts['where'], $this->sqlParts['groupBy'], $this->sqlParts['having'], $this->sqlParts['orderBy'], new Limit($this->maxResults, $this->firstResult), $this->sqlParts['for_update'], ), ); } /** * @return string[] * * @throws QueryException */ private function getFromClauses(): array { $fromClauses = []; $knownAliases = []; // Loop through all FROM clauses foreach ($this->sqlParts['from'] as $from) { if ($from['alias'] === null) { $tableSql = $from['table']; $tableReference = $from['table']; } else { $tableSql = $from['table'] . ' ' . $from['alias']; $tableReference = $from['alias']; } $knownAliases[$tableReference] = true; $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases); } $this->verifyAllAliasesAreKnown($knownAliases); return $fromClauses; } /** * @param array<string,true> $knownAliases * * @throws QueryException */ private function verifyAllAliasesAreKnown(array $knownAliases): void { foreach ($this->sqlParts['join'] as $fromAlias => $joins) { if (! isset($knownAliases[$fromAlias])) { throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases)); } } } /** * Converts this instance into an INSERT string in SQL. */ private function getSQLForInsert(): string { return 'INSERT INTO ' . $this->sqlParts['from']['table'] . ' (' . implode(', ', array_keys($this->sqlParts['values'])) . ')' . ' VALUES(' . implode(', ', $this->sqlParts['values']) . ')'; } /** * Converts this instance into an UPDATE string in SQL. */ private function getSQLForUpdate(): string { $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); return 'UPDATE ' . $table . ' SET ' . implode(', ', $this->sqlParts['set']) . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); } /** * Converts this instance into a DELETE string in SQL. */ private function getSQLForDelete(): string { $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); return 'DELETE FROM ' . $table . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); } /** * Gets a string representation of this QueryBuilder which corresponds to * the final SQL query being constructed. * * @return string The string representation of this QueryBuilder. */ public function __toString() { return $this->getSQL(); } /** * Creates a new named parameter and bind the value $value to it. * * This method provides a shortcut for {@see Statement::bindValue()} * when using prepared statements. * * The parameter $value specifies the value that you want to bind. If * $placeholder is not provided createNamedParameter() will automatically * create a placeholder for you. An automatic placeholder will be of the * name ':dcValue1', ':dcValue2' etc. * * Example: * <code> * $value = 2; * $q->eq( 'id', $q->createNamedParameter( $value ) ); * $stmt = $q->executeQuery(); // executed with 'id = 2' * </code> * * @link http://www.zetacomponents.org * * @param mixed $value * @param int|string|Type|null $type * @param string $placeHolder The name to bind with. The string must start with a colon ':'. * * @return string the placeholder name used. */ public function createNamedParameter($value, $type = ParameterType::STRING, $placeHolder = null) { if ($placeHolder === null) { $this->boundCounter++; $placeHolder = ':dcValue' . $this->boundCounter; } $this->setParameter(substr($placeHolder, 1), $value, $type); return $placeHolder; } /** * Creates a new positional parameter and bind the given value to it. * * Attention: If you are using positional parameters with the query builder you have * to be very careful to bind all parameters in the order they appear in the SQL * statement , otherwise they get bound in the wrong order which can lead to serious * bugs in your code. * * Example: * <code> * $qb = $conn->createQueryBuilder(); * $qb->select('u.*') * ->from('users', 'u') * ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING)) * ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING)) * </code> * * @param mixed $value * @param int|string|Type|null $type * * @return string */ public function createPositionalParameter($value, $type = ParameterType::STRING) { $this->setParameter($this->boundCounter, $value, $type); $this->boundCounter++; return '?'; } /** * @param string $fromAlias * @param array<string,true> $knownAliases * * @throws QueryException */ private function getSQLForJoins($fromAlias, array &$knownAliases): string { $sql = ''; if (isset($this->sqlParts['join'][$fromAlias])) { foreach ($this->sqlParts['join'][$fromAlias] as $join) { if (array_key_exists($join['joinAlias'], $knownAliases)) { throw QueryException::nonUniqueAlias((string) $join['joinAlias'], array_keys($knownAliases)); } $sql .= ' ' . strtoupper($join['joinType']) . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']; if ($join['joinCondition'] !== null) { $sql .= ' ON ' . $join['joinCondition']; } $knownAliases[$join['joinAlias']] = true; } foreach ($this->sqlParts['join'][$fromAlias] as $join) { $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases); } } return $sql; } /** * Deep clone of all expression objects in the SQL parts. * * @return void */ public function __clone() { foreach ($this->sqlParts as $part => $elements) { if (is_array($this->sqlParts[$part])) { foreach ($this->sqlParts[$part] as $idx => $element) { if (! is_object($element)) { continue; } $this->sqlParts[$part][$idx] = clone $element; } } elseif (is_object($elements)) { $this->sqlParts[$part] = clone $elements; } } foreach ($this->params as $name => $param) { if (! is_object($param)) { continue; } $this->params[$name] = clone $param; } } /** * Enables caching of the results of this query, for given amount of seconds * and optionally specified which key to use for the cache entry. * * @return $this */ public function enableResultCache(QueryCacheProfile $cacheProfile): self { $this->resultCacheProfile = $cacheProfile; return $this; } /** * Disables caching of the results of this query. * * @return $this */ public function disableResultCache(): self { $this->resultCacheProfile = null; return $this; } } dbal/src/Query/Expression/CompositeExpression.php 0000644 00000010456 15021222234 0016233 0 ustar 00 <?php namespace Doctrine\DBAL\Query\Expression; use Countable; use Doctrine\Deprecations\Deprecation; use ReturnTypeWillChange; use function array_merge; use function count; use function implode; /** * Composite expression is responsible to build a group of similar expression. */ class CompositeExpression implements Countable { /** * Constant that represents an AND composite expression. */ public const TYPE_AND = 'AND'; /** * Constant that represents an OR composite expression. */ public const TYPE_OR = 'OR'; /** * The instance type of composite expression. * * @var string */ private $type; /** * Each expression part of the composite expression. * * @var self[]|string[] */ private array $parts = []; /** * @internal Use the and() / or() factory methods. * * @param string $type Instance type of composite expression. * @param self[]|string[] $parts Composition of expressions to be joined on composite expression. */ public function __construct($type, array $parts = []) { $this->type = $type; $this->addMultiple($parts); Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3864', 'Do not use CompositeExpression constructor directly, use static and() and or() factory methods.', ); } /** * @param self|string $part * @param self|string ...$parts */ public static function and($part, ...$parts): self { return new self(self::TYPE_AND, array_merge([$part], $parts)); } /** * @param self|string $part * @param self|string ...$parts */ public static function or($part, ...$parts): self { return new self(self::TYPE_OR, array_merge([$part], $parts)); } /** * Adds multiple parts to composite expression. * * @deprecated This class will be made immutable. Use with() instead. * * @param self[]|string[] $parts * * @return CompositeExpression */ public function addMultiple(array $parts = []) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3844', 'CompositeExpression::addMultiple() is deprecated, use CompositeExpression::with() instead.', ); foreach ($parts as $part) { $this->add($part); } return $this; } /** * Adds an expression to composite expression. * * @deprecated This class will be made immutable. Use with() instead. * * @param mixed $part * * @return CompositeExpression */ public function add($part) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3844', 'CompositeExpression::add() is deprecated, use CompositeExpression::with() instead.', ); if ($part === null) { return $this; } if ($part instanceof self && count($part) === 0) { return $this; } $this->parts[] = $part; return $this; } /** * Returns a new CompositeExpression with the given parts added. * * @param self|string $part * @param self|string ...$parts */ public function with($part, ...$parts): self { $that = clone $this; $that->parts = array_merge($that->parts, [$part], $parts); return $that; } /** * Retrieves the amount of expressions on composite expression. * * @return int * @psalm-return int<0, max> */ #[ReturnTypeWillChange] public function count() { return count($this->parts); } /** * Retrieves the string representation of this composite expression. * * @return string */ public function __toString() { if ($this->count() === 1) { return (string) $this->parts[0]; } return '(' . implode(') ' . $this->type . ' (', $this->parts) . ')'; } /** * Returns the type of this composite expression (AND/OR). * * @return string */ public function getType() { return $this->type; } } dbal/src/Query/Expression/ExpressionBuilder.php 0000644 00000022612 15021222234 0015654 0 ustar 00 <?php namespace Doctrine\DBAL\Query\Expression; use Doctrine\DBAL\Connection; use Doctrine\Deprecations\Deprecation; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function sprintf; /** * ExpressionBuilder class is responsible to dynamically create SQL query parts. */ class ExpressionBuilder { public const EQ = '='; public const NEQ = '<>'; public const LT = '<'; public const LTE = '<='; public const GT = '>'; public const GTE = '>='; /** * The DBAL Connection. */ private Connection $connection; /** * Initializes a new <tt>ExpressionBuilder</tt>. * * @param Connection $connection The DBAL Connection. */ public function __construct(Connection $connection) { $this->connection = $connection; } /** * Creates a conjunction of the given expressions. * * @param string|CompositeExpression $expression * @param string|CompositeExpression ...$expressions */ public function and($expression, ...$expressions): CompositeExpression { return CompositeExpression::and($expression, ...$expressions); } /** * Creates a disjunction of the given expressions. * * @param string|CompositeExpression $expression * @param string|CompositeExpression ...$expressions */ public function or($expression, ...$expressions): CompositeExpression { return CompositeExpression::or($expression, ...$expressions); } /** * @deprecated Use `and()` instead. * * @param mixed $x Optional clause. Defaults = null, but requires * at least one defined when converting to string. * * @return CompositeExpression */ public function andX($x = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3851', 'ExpressionBuilder::andX() is deprecated, use ExpressionBuilder::and() instead.', ); return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); } /** * @deprecated Use `or()` instead. * * @param mixed $x Optional clause. Defaults = null, but requires * at least one defined when converting to string. * * @return CompositeExpression */ public function orX($x = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3851', 'ExpressionBuilder::orX() is deprecated, use ExpressionBuilder::or() instead.', ); return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args()); } /** * Creates a comparison expression. * * @param mixed $x The left expression. * @param string $operator One of the ExpressionBuilder::* constants. * @param mixed $y The right expression. * * @return string */ public function comparison($x, $operator, $y) { return $x . ' ' . $operator . ' ' . $y; } /** * Creates an equality comparison expression with the given arguments. * * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> = <right expr>. Example: * * [php] * // u.id = ? * $expr->eq('u.id', '?'); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function eq($x, $y) { return $this->comparison($x, self::EQ, $y); } /** * Creates a non equality comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> <> <right expr>. Example: * * [php] * // u.id <> 1 * $q->where($q->expr()->neq('u.id', '1')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function neq($x, $y) { return $this->comparison($x, self::NEQ, $y); } /** * Creates a lower-than comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> < <right expr>. Example: * * [php] * // u.id < ? * $q->where($q->expr()->lt('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function lt($x, $y) { return $this->comparison($x, self::LT, $y); } /** * Creates a lower-than-equal comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> <= <right expr>. Example: * * [php] * // u.id <= ? * $q->where($q->expr()->lte('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function lte($x, $y) { return $this->comparison($x, self::LTE, $y); } /** * Creates a greater-than comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> > <right expr>. Example: * * [php] * // u.id > ? * $q->where($q->expr()->gt('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function gt($x, $y) { return $this->comparison($x, self::GT, $y); } /** * Creates a greater-than-equal comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> >= <right expr>. Example: * * [php] * // u.id >= ? * $q->where($q->expr()->gte('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function gte($x, $y) { return $this->comparison($x, self::GTE, $y); } /** * Creates an IS NULL expression with the given arguments. * * @param string $x The expression to be restricted by IS NULL. * * @return string */ public function isNull($x) { return $x . ' IS NULL'; } /** * Creates an IS NOT NULL expression with the given arguments. * * @param string $x The expression to be restricted by IS NOT NULL. * * @return string */ public function isNotNull($x) { return $x . ' IS NOT NULL'; } /** * Creates a LIKE() comparison expression with the given arguments. * * @param string $x The expression to be inspected by the LIKE comparison * @param mixed $y The pattern to compare against * * @return string */ public function like($x, $y/*, ?string $escapeChar = null */) { return $this->comparison($x, 'LIKE', $y) . (func_num_args() >= 3 ? sprintf(' ESCAPE %s', func_get_arg(2)) : ''); } /** * Creates a NOT LIKE() comparison expression with the given arguments. * * @param string $x The expression to be inspected by the NOT LIKE comparison * @param mixed $y The pattern to compare against * * @return string */ public function notLike($x, $y/*, ?string $escapeChar = null */) { return $this->comparison($x, 'NOT LIKE', $y) . (func_num_args() >= 3 ? sprintf(' ESCAPE %s', func_get_arg(2)) : ''); } /** * Creates an IN () comparison expression with the given arguments. * * @param string $x The SQL expression to be matched against the set. * @param string|string[] $y The SQL expression or an array of SQL expressions representing the set. * * @return string */ public function in($x, $y) { return $this->comparison($x, 'IN', '(' . implode(', ', (array) $y) . ')'); } /** * Creates a NOT IN () comparison expression with the given arguments. * * @param string $x The SQL expression to be matched against the set. * @param string|string[] $y The SQL expression or an array of SQL expressions representing the set. * * @return string */ public function notIn($x, $y) { return $this->comparison($x, 'NOT IN', '(' . implode(', ', (array) $y) . ')'); } /** * Builds an SQL literal from a given input parameter. * * The usage of this method is discouraged. Use prepared statements * or {@see AbstractPlatform::quoteStringLiteral()} instead. * * @param mixed $input The parameter to be quoted. * @param int|null $type The type of the parameter. * * @return string */ public function literal($input, $type = null) { return $this->connection->quote($input, $type); } } dbal/src/Query/ForUpdate.php 0000644 00000000620 15021222234 0011733 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Query; /** @internal */ final class ForUpdate { private int $conflictResolutionMode; public function __construct(int $conflictResolutionMode) { $this->conflictResolutionMode = $conflictResolutionMode; } public function getConflictResolutionMode(): int { return $this->conflictResolutionMode; } } dbal/src/Statement.php 0000644 00000016726 15021222234 0010717 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function func_num_args; use function is_string; /** * A database abstraction-level statement that implements support for logging, DBAL mapping types, etc. */ class Statement { /** * The SQL statement. * * @var string */ protected $sql; /** * The bound parameters. * * @var mixed[] */ protected $params = []; /** * The parameter types. * * @var int[]|string[] */ protected $types = []; /** * The underlying driver statement. * * @var Driver\Statement */ protected $stmt; /** * The underlying database platform. * * @var AbstractPlatform */ protected $platform; /** * The connection this statement is bound to and executed on. * * @var Connection */ protected $conn; /** * Creates a new <tt>Statement</tt> for the given SQL and <tt>Connection</tt>. * * @internal The statement can be only instantiated by {@see Connection}. * * @param Connection $conn The connection for handling statement errors. * @param Driver\Statement $statement The underlying driver-level statement. * @param string $sql The SQL of the statement. * * @throws Exception */ public function __construct(Connection $conn, Driver\Statement $statement, string $sql) { $this->conn = $conn; $this->stmt = $statement; $this->sql = $sql; $this->platform = $conn->getDatabasePlatform(); } /** * Binds a parameter value to the statement. * * The value can optionally be bound with a DBAL mapping type. * If bound with a DBAL mapping type, the binding type is derived from the mapping * type and the value undergoes the conversion routines of the mapping type before * being bound. * * @param string|int $param The name or position of the parameter. * @param mixed $value The value of the parameter. * @param mixed $type Either a PDO binding type or a DBAL mapping type name or instance. * * @return bool TRUE on success, FALSE on failure. * * @throws Exception */ public function bindValue($param, $value, $type = ParameterType::STRING) { $this->params[$param] = $value; $this->types[$param] = $type; $bindingType = ParameterType::STRING; if ($type !== null) { if (is_string($type)) { $type = Type::getType($type); } $bindingType = $type; if ($type instanceof Type) { $value = $type->convertToDatabaseValue($value, $this->platform); $bindingType = $type->getBindingType(); } } try { return $this->stmt->bindValue($param, $value, $bindingType); } catch (Driver\Exception $e) { throw $this->conn->convertException($e); } } /** * Binds a parameter to a value by reference. * * Binding a parameter by reference does not support DBAL mapping types. * * @deprecated Use {@see bindValue()} instead. * * @param string|int $param The name or position of the parameter. * @param mixed $variable The reference to the variable to bind. * @param int $type The binding type. * @param int|null $length Must be specified when using an OUT bind * so that PHP allocates enough memory to hold the returned value. * * @return bool TRUE on success, FALSE on failure. * * @throws Exception */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5563', '%s is deprecated. Use bindValue() instead.', __METHOD__, ); $this->params[$param] = $variable; $this->types[$param] = $type; try { if (func_num_args() > 3) { return $this->stmt->bindParam($param, $variable, $type, $length); } return $this->stmt->bindParam($param, $variable, $type); } catch (Driver\Exception $e) { throw $this->conn->convertException($e); } } /** * Executes the statement with the currently bound parameters. * * @deprecated Statement::execute() is deprecated, use Statement::executeQuery() or executeStatement() instead * * @param mixed[]|null $params * * @throws Exception */ public function execute($params = null): Result { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4580', '%s() is deprecated, use Statement::executeQuery() or Statement::executeStatement() instead', __METHOD__, ); if ($params !== null) { $this->params = $params; } $logger = $this->conn->getConfiguration()->getSQLLogger(); if ($logger !== null) { $logger->startQuery($this->sql, $this->params, $this->types); } try { return new Result( $this->stmt->execute($params), $this->conn, ); } catch (Driver\Exception $ex) { throw $this->conn->convertExceptionDuringQuery($ex, $this->sql, $this->params, $this->types); } finally { if ($logger !== null) { $logger->stopQuery(); } } } /** * Executes the statement with the currently bound parameters and return result. * * @param mixed[] $params * * @throws Exception */ public function executeQuery(array $params = []): Result { if (func_num_args() > 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::executeQuery() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); } if ($params === []) { $params = null; // Workaround as long execute() exists and used internally. } return $this->execute($params); } /** * Executes the statement with the currently bound parameters and return affected rows. * * @param mixed[] $params * * @throws Exception */ public function executeStatement(array $params = []): int { if (func_num_args() > 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/5556', 'Passing $params to Statement::executeStatement() is deprecated. Bind parameters using' . ' Statement::bindParam() or Statement::bindValue() instead.', ); } if ($params === []) { $params = null; // Workaround as long execute() exists and used internally. } return $this->execute($params)->rowCount(); } /** * Gets the wrapped driver statement. * * @return Driver\Statement */ public function getWrappedStatement() { return $this->stmt; } } dbal/src/ParameterType.php 0000644 00000001726 15021222234 0011527 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Contains statement parameter types. */ final class ParameterType { /** * Represents the SQL NULL data type. */ public const NULL = 0; /** * Represents the SQL INTEGER data type. */ public const INTEGER = 1; /** * Represents the SQL CHAR, VARCHAR, or other string data type. * * @see \PDO::PARAM_STR */ public const STRING = 2; /** * Represents the SQL large object data type. */ public const LARGE_OBJECT = 3; /** * Represents a boolean data type. * * @see \PDO::PARAM_BOOL */ public const BOOLEAN = 5; /** * Represents a binary string data type. */ public const BINARY = 16; /** * Represents an ASCII string data type */ public const ASCII = 17; /** * This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } } dbal/README.md 0000644 00000010275 15021222234 0006723 0 ustar 00 # Doctrine DBAL | [5.0-dev][5.0] | [4.2-dev][4.2] | [4.1][4.1] | [3.9][3.9] | |:---------------------------------------------------:|:---------------------------------------------------:|:---------------------------------------------------:|:---------------------------------------------------:| | [![GitHub Actions][GA 5.0 image]][GA 5.0] | [![GitHub Actions][GA 4.2 image]][GA 4.2] | [![GitHub Actions][GA 4.1 image]][GA 4.1] | [![GitHub Actions][GA 3.9 image]][GA 3.9] | | [![AppVeyor][AppVeyor 5.0 image]][AppVeyor 5.0] | [![AppVeyor][AppVeyor 4.2 image]][AppVeyor 4.2] | [![AppVeyor][AppVeyor 4.1 image]][AppVeyor 4.1] | [![AppVeyor][AppVeyor 3.9 image]][AppVeyor 3.9] | | [![Code Coverage][Coverage 5.0 image]][CodeCov 5.0] | [![Code Coverage][Coverage 4.2 image]][CodeCov 4.2] | [![Code Coverage][Coverage 4.1 image]][CodeCov 4.1] | [![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.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 [Coverage 4.1 image]: https://codecov.io/gh/doctrine/dbal/branch/4.1.x/graph/badge.svg [4.1]: https://github.com/doctrine/dbal/tree/4.1.x [CodeCov 4.1]: https://codecov.io/gh/doctrine/dbal/branch/4.1.x [AppVeyor 4.1]: https://ci.appveyor.com/project/doctrine/dbal/branch/4.1.x [AppVeyor 4.1 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/4.1.x?svg=true [GA 4.1]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.1.x [GA 4.1 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=4.1.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 dbal/LICENSE 0000644 00000002051 15021222234 0006442 0 ustar 00 Copyright (c) 2006-2018 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. lexer/UPGRADE.md 0000644 00000001702 15021222234 0007265 0 ustar 00 Note about upgrading: Doctrine uses static and runtime mechanisms to raise awareness about deprecated code. - Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or Static Analysis tools (like Psalm, phpstan) - Use of our low-overhead runtime deprecation API, details: https://github.com/doctrine/deprecations/ # Upgrade to 3.0.0 `Doctrine\Common\Lexer\Token` no longer implements `ArrayAccess`. Parameter type declarations have been added to `Doctrine\Common\Lexer\AbstractLexer` and `Doctrine\Common\Lexer\Token`. You should add both parameter type declarations and return type declarations to your lexers, based on the `@return` phpdoc. # Upgrade to 2.0.0 `AbstractLexer::glimpse()` and `AbstractLexer::peek()` now return instances of `Doctrine\Common\Lexer\Token`, which is an array-like class Using it as an array is deprecated in favor of using properties of that class. Using `count()` on it is deprecated with no replacement. lexer/composer.json 0000644 00000002611 15021222234 0010376 0 ustar 00 { "name": "doctrine/lexer", "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", "license": "MIT", "type": "library", "keywords": [ "php", "parser", "lexer", "annotations", "docblock" ], "authors": [ { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, { "name": "Roman Borschel", "email": "roman@code-factory.org" }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], "homepage": "https://www.doctrine-project.org/projects/lexer.html", "require": { "php": "^8.1" }, "require-dev": { "doctrine/coding-standard": "^12", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^10.5", "psalm/plugin-phpunit": "^0.18.3", "vimeo/psalm": "^5.21" }, "autoload": { "psr-4": { "Doctrine\\Common\\Lexer\\": "src" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\Common\\Lexer\\": "tests" } }, "config": { "allow-plugins": { "composer/package-versions-deprecated": true, "dealerdirect/phpcodesniffer-composer-installer": true }, "sort-packages": true } } lexer/src/AbstractLexer.php 0000644 00000016426 15021222234 0011730 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\Lexer; use ReflectionClass; use UnitEnum; use function implode; use function preg_split; use function sprintf; use function substr; use const PREG_SPLIT_DELIM_CAPTURE; use const PREG_SPLIT_NO_EMPTY; use const PREG_SPLIT_OFFSET_CAPTURE; /** * Base class for writing simple lexers, i.e. for creating small DSLs. * * @template T of UnitEnum|string|int * @template V of string|int */ abstract class AbstractLexer { /** * Lexer original input string. */ private string $input; /** * Array of scanned tokens. * * @var list<Token<T, V>> */ private array $tokens = []; /** * Current lexer position in input string. */ private int $position = 0; /** * Current peek of current lexer position. */ private int $peek = 0; /** * The next token in the input. * * @var Token<T, V>|null */ public Token|null $lookahead; /** * The last matched/seen token. * * @var Token<T, V>|null */ public Token|null $token; /** * Composed regex for input parsing. * * @var non-empty-string|null */ private string|null $regex = null; /** * Sets the input data to be tokenized. * * The Lexer is immediately reset and the new input tokenized. * Any unprocessed tokens from any previous input are lost. * * @param string $input The input to be tokenized. * * @return void */ public function setInput(string $input) { $this->input = $input; $this->tokens = []; $this->reset(); $this->scan($input); } /** * Resets the lexer. * * @return void */ public function reset() { $this->lookahead = null; $this->token = null; $this->peek = 0; $this->position = 0; } /** * Resets the peek pointer to 0. * * @return void */ public function resetPeek() { $this->peek = 0; } /** * Resets the lexer position on the input to the given position. * * @param int $position Position to place the lexical scanner. * * @return void */ public function resetPosition(int $position = 0) { $this->position = $position; } /** * Retrieve the original lexer's input until a given position. * * @return string */ public function getInputUntilPosition(int $position) { return substr($this->input, 0, $position); } /** * Checks whether a given token matches the current lookahead. * * @param T $type * * @return bool * * @psalm-assert-if-true !=null $this->lookahead */ public function isNextToken(int|string|UnitEnum $type) { return $this->lookahead !== null && $this->lookahead->isA($type); } /** * Checks whether any of the given tokens matches the current lookahead. * * @param list<T> $types * * @return bool * * @psalm-assert-if-true !=null $this->lookahead */ public function isNextTokenAny(array $types) { return $this->lookahead !== null && $this->lookahead->isA(...$types); } /** * Moves to the next token in the input string. * * @return bool * * @psalm-assert-if-true !null $this->lookahead */ public function moveNext() { $this->peek = 0; $this->token = $this->lookahead; $this->lookahead = isset($this->tokens[$this->position]) ? $this->tokens[$this->position++] : null; return $this->lookahead !== null; } /** * Tells the lexer to skip input tokens until it sees a token with the given value. * * @param T $type The token type to skip until. * * @return void */ public function skipUntil(int|string|UnitEnum $type) { while ($this->lookahead !== null && ! $this->lookahead->isA($type)) { $this->moveNext(); } } /** * Checks if given value is identical to the given token. * * @return bool */ public function isA(string $value, int|string|UnitEnum $token) { return $this->getType($value) === $token; } /** * Moves the lookahead token forward. * * @return Token<T, V>|null The next token or NULL if there are no more tokens ahead. */ public function peek() { if (isset($this->tokens[$this->position + $this->peek])) { return $this->tokens[$this->position + $this->peek++]; } return null; } /** * Peeks at the next token, returns it and immediately resets the peek. * * @return Token<T, V>|null The next token or NULL if there are no more tokens ahead. */ public function glimpse() { $peek = $this->peek(); $this->peek = 0; return $peek; } /** * Scans the input string for tokens. * * @param string $input A query string. * * @return void */ protected function scan(string $input) { if (! isset($this->regex)) { $this->regex = sprintf( '/(%s)|%s/%s', implode(')|(', $this->getCatchablePatterns()), implode('|', $this->getNonCatchablePatterns()), $this->getModifiers(), ); } $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; $matches = preg_split($this->regex, $input, -1, $flags); if ($matches === false) { // Work around https://bugs.php.net/78122 $matches = [[$input, 0]]; } foreach ($matches as $match) { // Must remain before 'value' assignment since it can change content $firstMatch = $match[0]; $type = $this->getType($firstMatch); $this->tokens[] = new Token( $firstMatch, $type, $match[1], ); } } /** * Gets the literal for a given token. * * @param T $token * * @return int|string */ public function getLiteral(int|string|UnitEnum $token) { if ($token instanceof UnitEnum) { return $token::class . '::' . $token->name; } $className = static::class; $reflClass = new ReflectionClass($className); $constants = $reflClass->getConstants(); foreach ($constants as $name => $value) { if ($value === $token) { return $className . '::' . $name; } } return $token; } /** * Regex modifiers * * @return string */ protected function getModifiers() { return 'iu'; } /** * Lexical catchable patterns. * * @return string[] */ abstract protected function getCatchablePatterns(); /** * Lexical non-catchable patterns. * * @return string[] */ abstract protected function getNonCatchablePatterns(); /** * Retrieve token type. Also processes the token value if necessary. * * @return T|null * * @param-out V $value */ abstract protected function getType(string &$value); } lexer/src/Token.php 0000644 00000001775 15021222234 0010246 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\Lexer; use UnitEnum; use function in_array; /** * @template T of UnitEnum|string|int * @template V of string|int */ final class Token { /** * The string value of the token in the input string * * @readonly * @var V */ public string|int $value; /** * The type of the token (identifier, numeric, string, input parameter, none) * * @readonly * @var T|null */ public $type; /** * The position of the token in the input string * * @readonly */ public int $position; /** * @param V $value * @param T|null $type */ public function __construct(string|int $value, $type, int $position) { $this->value = $value; $this->type = $type; $this->position = $position; } /** @param T ...$types */ public function isA(...$types): bool { return in_array($this->type, $types, true); } } lexer/README.md 0000644 00000000557 15021222234 0007142 0 ustar 00 # Doctrine Lexer [](https://github.com/doctrine/lexer/actions) Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers. This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL). https://www.doctrine-project.org/projects/lexer.html lexer/LICENSE 0000644 00000002051 15021222234 0006657 0 ustar 00 Copyright (c) 2006-2018 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
| ver. 1.4 |
Github
|
.
| PHP 8.1.29 | Генерация страницы: 0.11 |
proxy
|
phpinfo
|
Настройка