Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Abstraction;
use Doctrine\DBAL\Driver\Result as DriverResult;
use Doctrine\DBAL\Exception;
use Traversable;
/**
* Abstraction-level result statement execution result. Provides additional methods on top
* of the driver-level interface.
*
* @deprecated
*/
interface Result extends DriverResult
{
/**
* Returns an iterator over the result set rows represented as numeric arrays.
*
* @return Traversable<int,array<int,mixed>>
*
* @throws Exception
*/
public function iterateNumeric(): Traversable;
/**
* Returns an iterator over the result set rows represented as associative arrays.
*
* @return Traversable<int,array<string,mixed>>
*
* @throws Exception
*/
public function iterateAssociative(): Traversable;
/**
* Returns an iterator over the values of the first column of the result set.
*
* @return Traversable<int,mixed>
*
* @throws Exception
*/
public function iterateColumn(): Traversable;
}

View File

@@ -0,0 +1,244 @@
<?php
namespace Doctrine\DBAL\Cache;
use ArrayIterator;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\FetchMode;
use InvalidArgumentException;
use IteratorAggregate;
use PDO;
use ReturnTypeWillChange;
use function array_merge;
use function array_values;
use function count;
use function reset;
/**
* @deprecated
*/
class ArrayStatement implements IteratorAggregate, ResultStatement, Result
{
/** @var mixed[] */
private $data;
/** @var int */
private $columnCount = 0;
/** @var int */
private $num = 0;
/** @var int */
private $defaultFetchMode = FetchMode::MIXED;
/**
* @param mixed[] $data
*/
public function __construct(array $data)
{
$this->data = $data;
if (! count($data)) {
return;
}
$this->columnCount = count($data[0]);
}
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*/
public function closeCursor()
{
$this->free();
return true;
}
/**
* {@inheritdoc}
*/
public function rowCount()
{
return count($this->data);
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return $this->columnCount;
}
/**
* {@inheritdoc}
*
* @deprecated Use one of the fetch- or iterate-related methods.
*/
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
{
if ($arg2 !== null || $arg3 !== null) {
throw new InvalidArgumentException('Caching layer does not support 2nd/3rd argument to setFetchMode()');
}
$this->defaultFetchMode = $fetchMode;
return true;
}
/**
* {@inheritdoc}
*
* @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead.
*/
#[ReturnTypeWillChange]
public function getIterator()
{
$data = $this->fetchAll();
return new ArrayIterator($data);
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
*/
public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
{
if (! isset($this->data[$this->num])) {
return false;
}
$row = $this->data[$this->num++];
$fetchMode = $fetchMode ?: $this->defaultFetchMode;
if ($fetchMode === FetchMode::ASSOCIATIVE) {
return $row;
}
if ($fetchMode === FetchMode::NUMERIC) {
return array_values($row);
}
if ($fetchMode === FetchMode::MIXED) {
return array_merge($row, array_values($row));
}
if ($fetchMode === FetchMode::COLUMN) {
return reset($row);
}
throw new InvalidArgumentException('Invalid fetch-style given for fetching result.');
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
*/
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
{
$rows = [];
while ($row = $this->fetch($fetchMode)) {
$rows[] = $row;
}
return $rows;
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchOne() instead.
*/
public function fetchColumn($columnIndex = 0)
{
$row = $this->fetch(FetchMode::NUMERIC);
// TODO: verify that return false is the correct behavior
return $row[$columnIndex] ?? false;
}
/**
* {@inheritdoc}
*/
public function fetchNumeric()
{
$row = $this->doFetch();
if ($row === false) {
return false;
}
return array_values($row);
}
/**
* {@inheritdoc}
*/
public function fetchAssociative()
{
return $this->doFetch();
}
/**
* {@inheritdoc}
*/
public function fetchOne()
{
$row = $this->doFetch();
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 free(): void
{
$this->data = [];
}
/**
* @return mixed|false
*/
private function doFetch()
{
if (! isset($this->data[$this->num])) {
return false;
}
return $this->data[$this->num++];
}
}

View File

@@ -0,0 +1,27 @@
<?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.');
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace Doctrine\DBAL\Cache;
use Doctrine\Common\Cache\Cache;
use Doctrine\DBAL\Types\Type;
use function hash;
use function serialize;
use function sha1;
/**
* Query Cache Profile handles the data relevant for query caching.
*
* It is a value object, setter methods return NEW instances.
*/
class QueryCacheProfile
{
/** @var Cache|null */
private $resultCacheDriver;
/** @var int */
private $lifetime = 0;
/** @var string|null */
private $cacheKey;
/**
* @param int $lifetime
* @param string|null $cacheKey
*/
public function __construct($lifetime = 0, $cacheKey = null, ?Cache $resultCache = null)
{
$this->lifetime = $lifetime;
$this->cacheKey = $cacheKey;
$this->resultCacheDriver = $resultCache;
}
/**
* @return Cache|null
*/
public function getResultCacheDriver()
{
return $this->resultCacheDriver;
}
/**
* @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 array<int, 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 string[]
*/
public function generateCacheKeys($sql, $params, $types, array $connectionParams = [])
{
$realCacheKey = 'query=' . $sql .
'&params=' . 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?
if ($this->cacheKey === null) {
$cacheKey = sha1($realCacheKey);
} else {
$cacheKey = $this->cacheKey;
}
return [$cacheKey, $realCacheKey];
}
/**
* @return QueryCacheProfile
*/
public function setResultCacheDriver(Cache $cache)
{
return new QueryCacheProfile($this->lifetime, $this->cacheKey, $cache);
}
/**
* @param string|null $cacheKey
*
* @return QueryCacheProfile
*/
public function setCacheKey($cacheKey)
{
return new QueryCacheProfile($this->lifetime, $cacheKey, $this->resultCacheDriver);
}
/**
* @param int $lifetime
*
* @return QueryCacheProfile
*/
public function setLifetime($lifetime)
{
return new QueryCacheProfile($lifetime, $this->cacheKey, $this->resultCacheDriver);
}
}

View File

@@ -0,0 +1,361 @@
<?php
namespace Doctrine\DBAL\Cache;
use ArrayIterator;
use Doctrine\Common\Cache\Cache;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\FetchMode;
use InvalidArgumentException;
use IteratorAggregate;
use PDO;
use ReturnTypeWillChange;
use function array_map;
use function array_merge;
use function array_values;
use function assert;
use function reset;
/**
* Cache statement for SQL results.
*
* A result is saved in multiple cache keys, there is the originally specified
* cache key which is just pointing to result rows by key. The following things
* have to be ensured:
*
* 1. lifetime of the original key has to be longer than that of all the individual rows keys
* 2. if any one row key is missing the query has to be re-executed.
*
* Also you have to realize that the cache will load the whole result into memory at once to ensure 2.
* This means that the memory usage for cached results might increase by using this feature.
*
* @deprecated
*/
class ResultCacheStatement implements IteratorAggregate, ResultStatement, Result
{
/** @var Cache */
private $resultCache;
/** @var string */
private $cacheKey;
/** @var string */
private $realKey;
/** @var int */
private $lifetime;
/** @var ResultStatement */
private $statement;
/** @var array<int,array<string,mixed>>|null */
private $data;
/** @var int */
private $defaultFetchMode = FetchMode::MIXED;
/**
* @param string $cacheKey
* @param string $realKey
* @param int $lifetime
*/
public function __construct(ResultStatement $stmt, Cache $resultCache, $cacheKey, $realKey, $lifetime)
{
$this->statement = $stmt;
$this->resultCache = $resultCache;
$this->cacheKey = $cacheKey;
$this->realKey = $realKey;
$this->lifetime = $lifetime;
}
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*/
public function closeCursor()
{
$this->free();
return true;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return $this->statement->columnCount();
}
/**
* {@inheritdoc}
*
* @deprecated Use one of the fetch- or iterate-related methods.
*/
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
{
$this->defaultFetchMode = $fetchMode;
return true;
}
/**
* {@inheritdoc}
*
* @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead.
*/
#[ReturnTypeWillChange]
public function getIterator()
{
$data = $this->fetchAll();
return new ArrayIterator($data);
}
/**
* Be warned that you will need to call this method until no rows are
* available for caching to happen.
*
* {@inheritdoc}
*
* @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
*/
public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
{
if ($this->data === null) {
$this->data = [];
}
$row = $this->statement->fetch(FetchMode::ASSOCIATIVE);
if ($row) {
$this->data[] = $row;
$fetchMode = $fetchMode ?: $this->defaultFetchMode;
if ($fetchMode === FetchMode::ASSOCIATIVE) {
return $row;
}
if ($fetchMode === FetchMode::NUMERIC) {
return array_values($row);
}
if ($fetchMode === FetchMode::MIXED) {
return array_merge($row, array_values($row));
}
if ($fetchMode === FetchMode::COLUMN) {
return reset($row);
}
throw new InvalidArgumentException('Invalid fetch-style given for caching result.');
}
$this->saveToCache();
return false;
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
*/
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
{
$data = $this->statement->fetchAll(FetchMode::ASSOCIATIVE, $fetchArgument, $ctorArgs);
$this->data = $data;
$this->saveToCache();
if ($fetchMode === FetchMode::NUMERIC) {
foreach ($data as $i => $row) {
$data[$i] = array_values($row);
}
} elseif ($fetchMode === FetchMode::MIXED) {
foreach ($data as $i => $row) {
$data[$i] = array_merge($row, array_values($row));
}
} elseif ($fetchMode === FetchMode::COLUMN) {
foreach ($data as $i => $row) {
$data[$i] = reset($row);
}
}
return $data;
}
/**
* Be warned that you will need to call this method until no rows are
* available for caching to happen.
*
* {@inheritdoc}
*
* @deprecated Use fetchOne() instead.
*/
public function fetchColumn($columnIndex = 0)
{
$row = $this->fetch(FetchMode::NUMERIC);
// TODO: verify that return false is the correct behavior
return $row[$columnIndex] ?? false;
}
/**
* Be warned that you will need to call this method until no rows are
* available for caching to happen.
*
* {@inheritdoc}
*/
public function fetchNumeric()
{
$row = $this->doFetch();
if ($row === false) {
return false;
}
return array_values($row);
}
/**
* Be warned that you will need to call this method until no rows are
* available for caching to happen.
*
* {@inheritdoc}
*/
public function fetchAssociative()
{
return $this->doFetch();
}
/**
* Be warned that you will need to call this method until no rows are
* available for caching to happen.
*
* {@inheritdoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritdoc}
*/
public function fetchAllNumeric(): array
{
if ($this->statement instanceof Result) {
$data = $this->statement->fetchAllAssociative();
} else {
$data = $this->statement->fetchAll(FetchMode::ASSOCIATIVE);
}
$this->data = $data;
$this->saveToCache();
return array_map('array_values', $data);
}
/**
* {@inheritdoc}
*/
public function fetchAllAssociative(): array
{
if ($this->statement instanceof Result) {
$data = $this->statement->fetchAllAssociative();
} else {
$data = $this->statement->fetchAll(FetchMode::ASSOCIATIVE);
}
$this->data = $data;
$this->saveToCache();
return $data;
}
/**
* {@inheritdoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
/**
* Returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement
* executed by the corresponding object.
*
* If the last SQL statement executed by the associated Statement object was a SELECT statement,
* some databases may return the number of rows returned by that statement. However,
* this behaviour is not guaranteed for all databases and should not be
* relied on for portable applications.
*
* @return int|string The number of rows.
*/
public function rowCount()
{
assert($this->statement instanceof Statement);
return $this->statement->rowCount();
}
public function free(): void
{
$this->data = null;
}
/**
* @return array<string,mixed>|false
*
* @throws Exception
*/
private function doFetch()
{
if ($this->data === null) {
$this->data = [];
}
if ($this->statement instanceof Result) {
$row = $this->statement->fetchAssociative();
} else {
$row = $this->statement->fetch(FetchMode::ASSOCIATIVE);
}
if ($row !== false) {
$this->data[] = $row;
return $row;
}
$this->saveToCache();
return false;
}
private function saveToCache(): void
{
if ($this->data === null) {
return;
}
$data = $this->resultCache->fetch($this->cacheKey);
if (! $data) {
$data = [];
}
$data[$this->realKey] = $this->data;
$this->resultCache->save($this->cacheKey, $data, $this->lifetime);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Doctrine\DBAL;
use PDO;
/**
* Contains portable column case conversions.
*/
final class ColumnCase
{
/**
* Convert column names to upper case.
*
* @see \PDO::CASE_UPPER
*/
public const UPPER = PDO::CASE_UPPER;
/**
* Convert column names to lower case.
*
* @see \PDO::CASE_LOWER
*/
public const LOWER = PDO::CASE_LOWER;
/**
* This class cannot be instantiated.
*
* @codeCoverageIgnore
*/
private function __construct()
{
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace Doctrine\DBAL;
use Doctrine\Common\Cache\Cache;
use Doctrine\DBAL\Logging\SQLLogger;
use Doctrine\DBAL\Schema\AbstractAsset;
use Doctrine\Deprecations\Deprecation;
use function preg_match;
/**
* Configuration container for the Doctrine DBAL.
*
* Internal note: When adding a new configuration option just write a getter/setter
* pair and add the option to the _attributes array with a proper default value.
*/
class Configuration
{
/**
* The attributes that are contained in the configuration.
* Values are default values.
*
* @var mixed[]
*/
protected $_attributes = [];
/**
* Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled.
*
* @return void
*/
public function setSQLLogger(?SQLLogger $logger = null)
{
$this->_attributes['sqlLogger'] = $logger;
}
/**
* Gets the SQL logger that is used.
*
* @return SQLLogger|null
*/
public function getSQLLogger()
{
return $this->_attributes['sqlLogger'] ?? null;
}
/**
* Gets the cache driver implementation that is used for query result caching.
*
* @return Cache|null
*/
public function getResultCacheImpl()
{
return $this->_attributes['resultCacheImpl'] ?? null;
}
/**
* Sets the cache driver implementation that is used for query result caching.
*
* @return void
*/
public function setResultCacheImpl(Cache $cacheImpl)
{
$this->_attributes['resultCacheImpl'] = $cacheImpl;
}
/**
* Sets the filter schema assets expression.
*
* Only include tables/sequences matching the filter expression regexp in
* schema instances generated for the active connection when calling
* {AbstractSchemaManager#createSchema()}.
*
* @deprecated Use Configuration::setSchemaAssetsFilter() instead
*
* @param string|null $filterExpression
*
* @return void
*/
public function setFilterSchemaAssetsExpression($filterExpression)
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/3316',
'Configuration::setFilterSchemaAssetsExpression() is deprecated, use setSchemaAssetsFilter() instead.'
);
$this->_attributes['filterSchemaAssetsExpression'] = $filterExpression;
if ($filterExpression) {
$this->_attributes['filterSchemaAssetsExpressionCallable']
= $this->buildSchemaAssetsFilterFromExpression($filterExpression);
} else {
$this->_attributes['filterSchemaAssetsExpressionCallable'] = null;
}
}
/**
* Returns filter schema assets expression.
*
* @deprecated Use Configuration::getSchemaAssetsFilter() instead
*
* @return string|null
*/
public function getFilterSchemaAssetsExpression()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/3316',
'Configuration::getFilterSchemaAssetsExpression() is deprecated, use getSchemaAssetsFilter() instead.'
);
return $this->_attributes['filterSchemaAssetsExpression'] ?? null;
}
/**
* @param string $filterExpression
*
* @return callable(string|AbstractAsset)
*/
private function buildSchemaAssetsFilterFromExpression($filterExpression): callable
{
return static function ($assetName) use ($filterExpression) {
if ($assetName instanceof AbstractAsset) {
$assetName = $assetName->getName();
}
return preg_match($filterExpression, $assetName);
};
}
/**
* Sets the callable to use to filter schema assets.
*/
public function setSchemaAssetsFilter(?callable $callable = null): ?callable
{
$this->_attributes['filterSchemaAssetsExpression'] = null;
return $this->_attributes['filterSchemaAssetsExpressionCallable'] = $callable;
}
/**
* Returns the callable to use to filter schema assets.
*/
public function getSchemaAssetsFilter(): ?callable
{
return $this->_attributes['filterSchemaAssetsExpressionCallable'] ?? null;
}
/**
* 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.
*
* @return void
*/
public function setAutoCommit($autoCommit)
{
$this->_attributes['autoCommit'] = (bool) $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()
{
return $this->_attributes['autoCommit'] ?? true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
<?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.');
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Doctrine\DBAL\Connections;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\DriverManager;
use Doctrine\Deprecations\Deprecation;
use InvalidArgumentException;
/**
* @deprecated Use PrimaryReadReplicaConnection instead
*
* @psalm-import-type Params from DriverManager
*/
class MasterSlaveConnection extends PrimaryReadReplicaConnection
{
/**
* Creates Primary Replica Connection.
*
* @internal The connection can be only instantiated by the driver manager.
*
* @param array<string,mixed> $params
* @psalm-param Params $params
* @phpstan-param array<string,mixed> $params
*
* @throws InvalidArgumentException
*/
public function __construct(
array $params,
Driver $driver,
?Configuration $config = null,
?EventManager $eventManager = null
) {
$this->deprecated(self::class, PrimaryReadReplicaConnection::class);
if (isset($params['master'])) {
$this->deprecated('Params key "master"', '"primary"');
$params['primary'] = $params['master'];
}
if (isset($params['slaves'])) {
$this->deprecated('Params key "slaves"', '"replica"');
$params['replica'] = $params['slaves'];
}
if (isset($params['keepSlave'])) {
$this->deprecated('Params key "keepSlave"', '"keepReplica"');
$params['keepReplica'] = $params['keepSlave'];
}
parent::__construct($params, $driver, $config, $eventManager);
}
/**
* Checks if the connection is currently towards the primary or not.
*/
public function isConnectedToMaster(): bool
{
$this->deprecated('isConnectedtoMaster()', 'isConnectedToPrimary()');
return $this->isConnectedToPrimary();
}
/**
* @param string|null $connectionName
*
* @return bool
*/
public function connect($connectionName = null)
{
if ($connectionName === 'master') {
$connectionName = 'primary';
$this->deprecated('connect("master")', 'ensureConnectedToPrimary()');
}
if ($connectionName === 'slave') {
$connectionName = 'replica';
$this->deprecated('connect("slave")', 'ensureConnectedToReplica()');
}
return $this->performConnect($connectionName);
}
private function deprecated(string $thing, string $instead): void
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4054',
'%s is deprecated since doctrine/dbal 2.11 and will be removed in 3.0, use %s instead.',
$thing,
$instead
);
}
}

View File

@@ -0,0 +1,442 @@
<?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\DriverManager;
use Doctrine\DBAL\Event\ConnectionEventArgs;
use Doctrine\DBAL\Events;
use InvalidArgumentException;
use function array_rand;
use function assert;
use function count;
use function func_get_args;
/**
* 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 'exec', 'executeUpdate', 'executeStatement', 'insert', 'delete', 'update', 'createSavepoint',
* 'releaseSavepoint', 'beginTransaction', 'rollback', 'commit', 'query' 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
* @phpstan-param array<string,mixed> $params
*
* @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)) {
$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
*/
protected function connectTo($connectionName)
{
$params = $this->getParams();
$driverOptions = $params['driverOptions'] ?? [];
$connectionParams = $this->chooseConnectionConfiguration($connectionName, $params);
$user = $connectionParams['user'] ?? null;
$password = $connectionParams['password'] ?? null;
return $this->_driver->connect($connectionParams, $user, $password, $driverOptions);
}
/**
* @param string $connectionName
* @param mixed[] $params
*
* @return mixed
*/
protected function chooseConnectionConfiguration($connectionName, $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}
*
* @deprecated Use {@link executeStatement()} instead.
*/
public function executeUpdate($sql, array $params = [], array $types = [])
{
$this->ensureConnectedToPrimary();
return parent::executeUpdate($sql, $params, $types);
}
/**
* {@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 delete($table, array $criteria, array $types = [])
{
$this->ensureConnectedToPrimary();
return parent::delete($table, $criteria, $types);
}
/**
* {@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 update($table, array $data, array $criteria, array $types = [])
{
$this->ensureConnectedToPrimary();
return parent::update($table, $data, $criteria, $types);
}
/**
* {@inheritDoc}
*/
public function insert($table, array $data, array $types = [])
{
$this->ensureConnectedToPrimary();
return parent::insert($table, $data, $types);
}
/**
* {@inheritDoc}
*/
public function exec($sql)
{
$this->ensureConnectedToPrimary();
return parent::exec($sql);
}
/**
* {@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);
}
/**
* {@inheritDoc}
*/
public function query()
{
$this->ensureConnectedToPrimary();
assert($this->_conn instanceof DriverConnection);
$args = func_get_args();
$logger = $this->getConfiguration()->getSQLLogger();
if ($logger) {
$logger->startQuery($args[0]);
}
$statement = $this->_conn->query(...$args);
$statement->setFetchMode($this->defaultFetchMode);
if ($logger) {
$logger->stopQuery();
}
return $statement;
}
/**
* {@inheritDoc}
*/
public function prepare($sql)
{
$this->ensureConnectedToPrimary();
return parent::prepare($sql);
}
}

View File

@@ -0,0 +1,314 @@
<?php
namespace Doctrine\DBAL;
use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException;
use Doctrine\DBAL\Driver\ExceptionConverterDriver;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Throwable;
use function array_map;
use function bin2hex;
use function get_class;
use function gettype;
use function implode;
use function is_object;
use function is_resource;
use function is_string;
use function json_encode;
use function preg_replace;
use function spl_object_hash;
use function sprintf;
/**
* @deprecated Use {@link Exception} instead
*
* @psalm-immutable
*/
class DBALException extends \Exception
{
/**
* @param string $method
*
* @return Exception
*/
public static function notSupported($method)
{
return new Exception(sprintf("Operation '%s' is not supported by platform.", $method));
}
/**
* @deprecated Use {@link invalidPlatformType()} instead.
*/
public static function invalidPlatformSpecified(): self
{
return new Exception(
"Invalid 'platform' option specified, need to give an instance of " . AbstractPlatform::class . '.'
);
}
/**
* @param mixed $invalidPlatform
*/
public static function invalidPlatformType($invalidPlatform): self
{
if (is_object($invalidPlatform)) {
return new Exception(
sprintf(
"Option 'platform' must be a subtype of '%s', instance of '%s' given",
AbstractPlatform::class,
get_class($invalidPlatform)
)
);
}
return new Exception(
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.
*
* @return Exception
*/
public static function invalidPlatformVersionSpecified($version, $expectedFormat)
{
return new Exception(
sprintf(
'Invalid platform version "%s" specified. ' .
'The platform version has to be specified in the format: "%s".',
$version,
$expectedFormat
)
);
}
/**
* @deprecated Passing a PDO instance in connection parameters is deprecated.
*
* @return Exception
*/
public static function invalidPdoInstance()
{
return new Exception(
"The 'pdo' option was used in DriverManager::getConnection() but no " .
'instance of PDO was given.'
);
}
/**
* @param string|null $url The URL that was provided in the connection parameters (if any).
*
* @return Exception
*/
public static function driverRequired($url = null)
{
if ($url) {
return new Exception(
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 Exception("The options 'driver' or 'driverClass' are mandatory if no PDO " .
'instance is given to DriverManager::getConnection().');
}
/**
* @param string $unknownDriverName
* @param string[] $knownDrivers
*
* @return Exception
*/
public static function unknownDriver($unknownDriverName, array $knownDrivers)
{
return new Exception("The given 'driver' " . $unknownDriverName . ' is unknown, ' .
'Doctrine currently supports only the following drivers: ' . implode(', ', $knownDrivers));
}
/**
* @deprecated
*
* @param string $sql
* @param mixed[] $params
*
* @return Exception
*/
public static function driverExceptionDuringQuery(Driver $driver, Throwable $driverEx, $sql, array $params = [])
{
$msg = "An exception occurred while executing '" . $sql . "'";
if ($params) {
$msg .= ' with params ' . self::formatParameters($params);
}
$msg .= ":\n\n" . $driverEx->getMessage();
return self::wrapException($driver, $driverEx, $msg);
}
/**
* @deprecated
*
* @return Exception
*/
public static function driverException(Driver $driver, Throwable $driverEx)
{
return self::wrapException($driver, $driverEx, 'An exception occurred in driver: ' . $driverEx->getMessage());
}
/**
* @return Exception
*/
private static function wrapException(Driver $driver, Throwable $driverEx, string $msg)
{
if ($driverEx instanceof DriverException) {
return $driverEx;
}
if ($driver instanceof ExceptionConverterDriver && $driverEx instanceof DeprecatedDriverException) {
return $driver->convertException($msg, $driverEx);
}
return new Exception($msg, 0, $driverEx);
}
/**
* Returns a human-readable representation of an array of parameters.
* This properly handles binary data by returning a hex representation.
*
* @param mixed[] $params
*
* @return string
*/
private static function formatParameters(array $params)
{
return '[' . implode(', ', array_map(static function ($param) {
if (is_resource($param)) {
return (string) $param;
}
$json = @json_encode($param);
if (! is_string($json) || $json === 'null' && is_string($param)) {
// JSON encoding failed, this is not a UTF-8 string.
return sprintf('"%s"', preg_replace('/.{2}/', '\\x$0', bin2hex($param)));
}
return $json;
}, $params)) . ']';
}
/**
* @param string $wrapperClass
*
* @return Exception
*/
public static function invalidWrapperClass($wrapperClass)
{
return new Exception("The given 'wrapperClass' " . $wrapperClass . ' has to be a ' .
'subtype of \Doctrine\DBAL\Connection.');
}
/**
* @param string $driverClass
*
* @return Exception
*/
public static function invalidDriverClass($driverClass)
{
return new Exception(
"The given 'driverClass' " . $driverClass . ' has to implement the ' . Driver::class . ' interface.'
);
}
/**
* @param string $tableName
*
* @return Exception
*/
public static function invalidTableName($tableName)
{
return new Exception('Invalid table name specified: ' . $tableName);
}
/**
* @param string $tableName
*
* @return Exception
*/
public static function noColumnsSpecifiedForTable($tableName)
{
return new Exception('No columns specified for table ' . $tableName);
}
/**
* @return Exception
*/
public static function limitOffsetInvalid()
{
return new Exception('Invalid Offset in Limit Query, it has to be larger than or equal to 0.');
}
/**
* @param string $name
*
* @return Exception
*/
public static function typeExists($name)
{
return new Exception('Type ' . $name . ' already exists.');
}
/**
* @param string $name
*
* @return Exception
*/
public static function unknownColumnType($name)
{
return new Exception('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.');
}
/**
* @param string $name
*
* @return Exception
*/
public static function typeNotFound($name)
{
return new Exception('Type to be overwritten ' . $name . ' does not exist.');
}
public static function typeNotRegistered(Type $type): self
{
return new Exception(
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 Exception(
sprintf('Type of the class %s@%s is already registered.', get_class($type), spl_object_hash($type))
);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Doctrine\DBAL;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
/**
* Driver interface.
* Interface that all DBAL drivers must implement.
*/
interface Driver
{
/**
* Attempts to create a connection with the database.
*
* The usage of NULL to indicate empty username or password is deprecated. Use an empty string instead.
*
* @param mixed[] $params All connection parameters passed by the user.
* @param string|null $username The username to use when connecting.
* @param string|null $password The password to use when connecting.
* @param mixed[] $driverOptions The driver options to use when connecting.
*
* @return \Doctrine\DBAL\Driver\Connection The database connection.
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = []);
/**
* 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.
*
* @return AbstractSchemaManager
*/
public function getSchemaManager(Connection $conn);
/**
* Gets the name of the driver.
*
* @deprecated
*
* @return string The name of the driver.
*/
public function getName();
/**
* Gets the name of the database connected to for this driver.
*
* @deprecated Use Connection::getDatabase() instead.
*
* @return string The name of the database.
*/
public function getDatabase(Connection $conn);
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as TheDriverException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Schema\DB2SchemaManager;
/**
* Abstract base implementation of the {@link Driver} interface for IBM DB2 based drivers.
*/
abstract class AbstractDB2Driver implements Driver
{
/**
* {@inheritdoc}
*
* @deprecated Use Connection::getDatabase() instead.
*/
public function getDatabase(Connection $conn)
{
$params = $conn->getParams();
return $params['dbname'];
}
/**
* {@inheritdoc}
*/
public function getDatabasePlatform()
{
return new DB2Platform();
}
/**
* {@inheritdoc}
*/
public function getSchemaManager(Connection $conn)
{
return new DB2SchemaManager($conn);
}
/**
* @param string $message
*
* @return DriverException
*/
public function convertException($message, TheDriverException $exception)
{
return new DriverException($message, $exception);
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Doctrine\DBAL\Driver;
/**
* @deprecated
*
* @psalm-immutable
*/
class AbstractDriverException extends AbstractException
{
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver;
use Doctrine\Deprecations\Deprecation;
use Exception as BaseException;
use Throwable;
/**
* Base implementation of the {@link Exception} interface.
*
* @internal
*
* @psalm-immutable
*/
abstract class AbstractException extends BaseException implements DriverException
{
/**
* The driver specific error code.
*
* @var int|string|null
*/
private $errorCode;
/**
* The SQLSTATE of the driver.
*
* @var string|null
*/
private $sqlState;
/**
* @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|string|null $errorCode The driver specific error code if any.
*/
public function __construct($message, $sqlState = null, $errorCode = null, ?Throwable $previous = null)
{
parent::__construct($message, 0, $previous);
$this->errorCode = $errorCode;
$this->sqlState = $sqlState;
}
/**
* {@inheritdoc}
*/
public function getErrorCode()
{
/** @psalm-suppress ImpureMethodCall */
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4112',
'Driver\AbstractException::getErrorCode() is deprecated, use getSQLState() or getCode() instead.'
);
return $this->errorCode;
}
/**
* {@inheritdoc}
*/
public function getSQLState()
{
return $this->sqlState;
}
}

View File

@@ -0,0 +1,256 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\ConnectionLost;
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\Platforms\MariaDb1027Platform;
use Doctrine\DBAL\Platforms\MySQL57Platform;
use Doctrine\DBAL\Platforms\MySQL80Platform;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Doctrine\DBAL\Schema\MySqlSchemaManager;
use Doctrine\DBAL\VersionAwarePlatformDriver;
use function assert;
use function preg_match;
use function stripos;
use function version_compare;
/**
* Abstract base implementation of the {@link Driver} interface for MySQL based drivers.
*/
abstract class AbstractMySQLDriver implements Driver, ExceptionConverterDriver, VersionAwarePlatformDriver
{
/**
* {@inheritdoc}
*
* @deprecated
*
* @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 convertException($message, DeprecatedDriverException $exception)
{
switch ($exception->getErrorCode()) {
case '1213':
return new DeadlockException($message, $exception);
case '1205':
return new LockWaitTimeoutException($message, $exception);
case '1050':
return new TableExistsException($message, $exception);
case '1051':
case '1146':
return new TableNotFoundException($message, $exception);
case '1216':
case '1217':
case '1451':
case '1452':
case '1701':
return new ForeignKeyConstraintViolationException($message, $exception);
case '1062':
case '1557':
case '1569':
case '1586':
return new UniqueConstraintViolationException($message, $exception);
case '1054':
case '1166':
case '1611':
return new InvalidFieldNameException($message, $exception);
case '1052':
case '1060':
case '1110':
return new NonUniqueFieldNameException($message, $exception);
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($message, $exception);
case '1044':
case '1045':
case '1046':
case '1049':
case '1095':
case '1142':
case '1143':
case '1227':
case '1370':
case '1429':
case '2002':
case '2005':
return new ConnectionException($message, $exception);
case '2006':
return new ConnectionLost($message, $exception);
case '1048':
case '1121':
case '1138':
case '1171':
case '1252':
case '1263':
case '1364':
case '1566':
return new NotNullConstraintViolationException($message, $exception);
}
return new DriverException($message, $exception);
}
/**
* {@inheritdoc}
*
* @throws Exception
*/
public function createDatabasePlatformForVersion($version)
{
$mariadb = stripos($version, 'mariadb') !== false;
if ($mariadb && version_compare($this->getMariaDbMysqlVersionNumber($version), '10.2.7', '>=')) {
return new MariaDb1027Platform();
}
if (! $mariadb) {
$oracleMysqlVersion = $this->getOracleMysqlVersionNumber($version);
if (version_compare($oracleMysqlVersion, '8', '>=')) {
return new MySQL80Platform();
}
if (version_compare($oracleMysqlVersion, '5.7.9', '>=')) {
return new MySQL57Platform();
}
}
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
)
) {
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 === null) {
$patchVersion = '9';
}
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 (
! preg_match(
'/^(?:5\.5\.5-)?(mariadb-)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)/i',
$versionString,
$versionParts
)
) {
throw Exception::invalidPlatformVersionSpecified(
$versionString,
'^(?:5\.5\.5-)?(mariadb-)?<major_version>.<minor_version>.<patch_version>'
);
}
return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch'];
}
/**
* {@inheritdoc}
*
* @deprecated Use Connection::getDatabase() instead.
*/
public function getDatabase(Connection $conn)
{
$params = $conn->getParams();
if (isset($params['dbname'])) {
return $params['dbname'];
}
$database = $conn->query('SELECT DATABASE()')->fetchColumn();
assert($database !== false);
return $database;
}
/**
* {@inheritdoc}
*
* @return MySqlPlatform
*/
public function getDatabasePlatform()
{
return new MySqlPlatform();
}
/**
* {@inheritdoc}
*
* @return MySqlSchemaManager
*/
public function getSchemaManager(Connection $conn)
{
return new MySqlSchemaManager($conn);
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractOracleDriver\EasyConnectString;
use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException;
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\Platforms\OraclePlatform;
use Doctrine\DBAL\Schema\OracleSchemaManager;
/**
* Abstract base implementation of the {@link Driver} interface for Oracle based drivers.
*/
abstract class AbstractOracleDriver implements Driver, ExceptionConverterDriver
{
/**
* {@inheritdoc}
*
* @deprecated
*/
public function convertException($message, DeprecatedDriverException $exception)
{
switch ($exception->getErrorCode()) {
case '1':
case '2299':
case '38911':
return new UniqueConstraintViolationException($message, $exception);
case '904':
return new InvalidFieldNameException($message, $exception);
case '918':
case '960':
return new NonUniqueFieldNameException($message, $exception);
case '923':
return new SyntaxErrorException($message, $exception);
case '942':
return new TableNotFoundException($message, $exception);
case '955':
return new TableExistsException($message, $exception);
case '1017':
case '12545':
return new ConnectionException($message, $exception);
case '1400':
return new NotNullConstraintViolationException($message, $exception);
case '2266':
case '2291':
case '2292':
return new ForeignKeyConstraintViolationException($message, $exception);
}
return new DriverException($message, $exception);
}
/**
* {@inheritdoc}
*
* @deprecated Use Connection::getDatabase() instead.
*/
public function getDatabase(Connection $conn)
{
$params = $conn->getParams();
return $params['user'];
}
/**
* {@inheritdoc}
*/
public function getDatabasePlatform()
{
return new OraclePlatform();
}
/**
* {@inheritdoc}
*/
public function getSchemaManager(Connection $conn)
{
return new OracleSchemaManager($conn);
}
/**
* Returns an appropriate Easy Connect String for the given parameters.
*
* @param mixed[] $params The connection parameters to return the Easy Connect String for.
*
* @return string
*/
protected function getEasyConnectString(array $params)
{
return (string) EasyConnectString::fromConnectionParameters($params);
}
}

View File

@@ -0,0 +1,121 @@
<?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
{
/** @var string */
private $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 (! empty($params['connectstring'])) {
return new self($params['connectstring']);
}
if (empty($params['host'])) {
return new self($params['dbname'] ?? '');
}
$connectData = [];
if (isset($params['servicename']) || isset($params['dbname'])) {
$serviceKey = 'SID';
if (! empty($params['service'])) {
$serviceKey = 'SERVICE_NAME';
}
$serviceName = $params['servicename'] ?? $params['dbname'];
$connectData[$serviceKey] = $serviceName;
}
if (! empty($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;
}
}

View File

@@ -0,0 +1,171 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
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\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Platforms\PostgreSQL100Platform;
use Doctrine\DBAL\Platforms\PostgreSQL91Platform;
use Doctrine\DBAL\Platforms\PostgreSQL92Platform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\DBAL\Schema\PostgreSqlSchemaManager;
use Doctrine\DBAL\VersionAwarePlatformDriver;
use function assert;
use function preg_match;
use function strpos;
use function version_compare;
/**
* Abstract base implementation of the {@link Driver} interface for PostgreSQL based drivers.
*/
abstract class AbstractPostgreSQLDriver implements Driver, ExceptionConverterDriver, VersionAwarePlatformDriver
{
/**
* {@inheritdoc}
*
* @deprecated
*
* @link http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
*/
public function convertException($message, DeprecatedDriverException $exception)
{
$sqlState = $exception->getSQLState();
switch ($sqlState) {
case '40001':
case '40P01':
return new DeadlockException($message, $exception);
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($message, $exception);
}
break;
case '23502':
return new NotNullConstraintViolationException($message, $exception);
case '23503':
return new ForeignKeyConstraintViolationException($message, $exception);
case '23505':
return new UniqueConstraintViolationException($message, $exception);
case '42601':
return new SyntaxErrorException($message, $exception);
case '42702':
return new NonUniqueFieldNameException($message, $exception);
case '42703':
return new InvalidFieldNameException($message, $exception);
case '42P01':
return new TableNotFoundException($message, $exception);
case '42P07':
return new TableExistsException($message, $exception);
case '08006':
return new Exception\ConnectionException($message, $exception);
case '7':
// Prior to fixing https://bugs.php.net/bug.php?id=64705 (PHP 7.3.22 and PHP 7.4.10),
// in some cases (mainly connection errors) the PDO exception wouldn't provide a SQLSTATE via its code.
// The exception code would be always set to 7 here.
// We have to match against the SQLSTATE in the error message in these cases.
if (strpos($exception->getMessage(), 'SQLSTATE[08006]') !== false) {
return new ConnectionException($message, $exception);
}
break;
}
return new DriverException($message, $exception);
}
/**
* {@inheritdoc}
*/
public function createDatabasePlatformForVersion($version)
{
if (! preg_match('/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?)?/', $version, $versionParts)) {
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;
switch (true) {
case version_compare($version, '10.0', '>='):
return new PostgreSQL100Platform();
case version_compare($version, '9.4', '>='):
return new PostgreSQL94Platform();
case version_compare($version, '9.2', '>='):
return new PostgreSQL92Platform();
case version_compare($version, '9.1', '>='):
return new PostgreSQL91Platform();
default:
return new PostgreSqlPlatform();
}
}
/**
* {@inheritdoc}
*
* @deprecated Use Connection::getDatabase() instead.
*/
public function getDatabase(Connection $conn)
{
$params = $conn->getParams();
if (isset($params['dbname'])) {
return $params['dbname'];
}
$database = $conn->query('SELECT CURRENT_DATABASE()')->fetchColumn();
assert($database !== false);
return $database;
}
/**
* {@inheritdoc}
*/
public function getDatabasePlatform()
{
return new PostgreSqlPlatform();
}
/**
* {@inheritdoc}
*/
public function getSchemaManager(Connection $conn)
{
return new PostgreSqlSchemaManager($conn);
}
}

View File

@@ -0,0 +1,170 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
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\Platforms\SQLAnywhere11Platform;
use Doctrine\DBAL\Platforms\SQLAnywhere12Platform;
use Doctrine\DBAL\Platforms\SQLAnywhere16Platform;
use Doctrine\DBAL\Platforms\SQLAnywherePlatform;
use Doctrine\DBAL\Schema\SQLAnywhereSchemaManager;
use Doctrine\DBAL\VersionAwarePlatformDriver;
use function assert;
use function preg_match;
use function version_compare;
/**
* Abstract base implementation of the {@link Driver} interface for SAP Sybase SQL Anywhere based drivers.
*
* @deprecated Support for SQLAnywhere will be removed in 3.0.
*/
abstract class AbstractSQLAnywhereDriver implements Driver, ExceptionConverterDriver, VersionAwarePlatformDriver
{
/**
* {@inheritdoc}
*
* @deprecated
*
* @link http://dcx.sybase.com/index.html#sa160/en/saerrors/sqlerror.html
*/
public function convertException($message, DeprecatedDriverException $exception)
{
switch ($exception->getErrorCode()) {
case '-306':
case '-307':
case '-684':
return new DeadlockException($message, $exception);
case '-210':
case '-1175':
case '-1281':
return new LockWaitTimeoutException($message, $exception);
case '-100':
case '-103':
case '-832':
return new ConnectionException($message, $exception);
case '-143':
return new InvalidFieldNameException($message, $exception);
case '-193':
case '-196':
return new UniqueConstraintViolationException($message, $exception);
case '-194':
case '-198':
return new ForeignKeyConstraintViolationException($message, $exception);
case '-144':
return new NonUniqueFieldNameException($message, $exception);
case '-184':
case '-195':
return new NotNullConstraintViolationException($message, $exception);
case '-131':
return new SyntaxErrorException($message, $exception);
case '-110':
return new TableExistsException($message, $exception);
case '-141':
case '-1041':
return new TableNotFoundException($message, $exception);
}
return new DriverException($message, $exception);
}
/**
* {@inheritdoc}
*/
public function createDatabasePlatformForVersion($version)
{
if (
! preg_match(
'/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+)(?:\.(?P<build>\d+))?)?)?/',
$version,
$versionParts
)
) {
throw Exception::invalidPlatformVersionSpecified(
$version,
'<major_version>.<minor_version>.<patch_version>.<build_version>'
);
}
$majorVersion = $versionParts['major'];
$minorVersion = $versionParts['minor'] ?? 0;
$patchVersion = $versionParts['patch'] ?? 0;
$buildVersion = $versionParts['build'] ?? 0;
$version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion . '.' . $buildVersion;
switch (true) {
case version_compare($version, '16', '>='):
return new SQLAnywhere16Platform();
case version_compare($version, '12', '>='):
return new SQLAnywhere12Platform();
case version_compare($version, '11', '>='):
return new SQLAnywhere11Platform();
default:
return new SQLAnywherePlatform();
}
}
/**
* {@inheritdoc}
*
* @deprecated Use Connection::getDatabase() instead.
*/
public function getDatabase(Connection $conn)
{
$params = $conn->getParams();
if (isset($params['dbname'])) {
return $params['dbname'];
}
$database = $conn->query('SELECT DB_NAME()')->fetchColumn();
assert($database !== false);
return $database;
}
/**
* {@inheritdoc}
*/
public function getDatabasePlatform()
{
return new SQLAnywhere12Platform();
}
/**
* {@inheritdoc}
*/
public function getSchemaManager(Connection $conn)
{
return new SQLAnywhereSchemaManager($conn);
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as TheDriverException;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\SQLServer2005Platform;
use Doctrine\DBAL\Platforms\SQLServer2008Platform;
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Schema\SQLServerSchemaManager;
use Doctrine\DBAL\VersionAwarePlatformDriver;
use function assert;
use function preg_match;
use function version_compare;
/**
* Abstract base implementation of the {@link Driver} interface for Microsoft SQL Server based drivers.
*/
abstract class AbstractSQLServerDriver implements Driver, VersionAwarePlatformDriver
{
/**
* {@inheritdoc}
*/
public function createDatabasePlatformForVersion($version)
{
if (
! preg_match(
'/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+)(?:\.(?P<build>\d+))?)?)?/',
$version,
$versionParts
)
) {
throw Exception::invalidPlatformVersionSpecified(
$version,
'<major_version>.<minor_version>.<patch_version>.<build_version>'
);
}
$majorVersion = $versionParts['major'];
$minorVersion = $versionParts['minor'] ?? 0;
$patchVersion = $versionParts['patch'] ?? 0;
$buildVersion = $versionParts['build'] ?? 0;
$version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion . '.' . $buildVersion;
switch (true) {
case version_compare($version, '11.00.2100', '>='):
return new SQLServer2012Platform();
case version_compare($version, '10.00.1600', '>='):
return new SQLServer2008Platform();
case version_compare($version, '9.00.1399', '>='):
return new SQLServer2005Platform();
default:
return new SQLServerPlatform();
}
}
/**
* {@inheritdoc}
*
* @deprecated Use Connection::getDatabase() instead.
*/
public function getDatabase(Connection $conn)
{
$params = $conn->getParams();
if (isset($params['dbname'])) {
return $params['dbname'];
}
$database = $conn->query('SELECT DB_NAME()')->fetchColumn();
assert($database !== false);
return $database;
}
/**
* {@inheritdoc}
*/
public function getDatabasePlatform()
{
return new SQLServer2008Platform();
}
/**
* {@inheritdoc}
*/
public function getSchemaManager(Connection $conn)
{
return new SQLServerSchemaManager($conn);
}
/**
* @param string $message
*
* @return DriverException
*/
public function convertException($message, TheDriverException $exception)
{
return new DriverException($message, $exception);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception;
use Doctrine\DBAL\Driver\AbstractDriverException;
/**
* @internal
*
* @psalm-immutable
*/
final class PortWithoutHost extends AbstractDriverException
{
public static function new(): self
{
return new self('Connection port specified without the host');
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DriverException;
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\Platforms\SqlitePlatform;
use Doctrine\DBAL\Schema\SqliteSchemaManager;
use function strpos;
/**
* Abstract base implementation of the {@link Driver} interface for SQLite based drivers.
*/
abstract class AbstractSQLiteDriver implements Driver, ExceptionConverterDriver
{
/**
* {@inheritdoc}
*
* @deprecated
*
* @link http://www.sqlite.org/c3ref/c_abort.html
*/
public function convertException($message, DeprecatedDriverException $exception)
{
if (strpos($exception->getMessage(), 'database is locked') !== false) {
return new LockWaitTimeoutException($message, $exception);
}
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($message, $exception);
}
if (
strpos($exception->getMessage(), 'may not be NULL') !== false ||
strpos($exception->getMessage(), 'NOT NULL constraint failed') !== false
) {
return new NotNullConstraintViolationException($message, $exception);
}
if (strpos($exception->getMessage(), 'no such table:') !== false) {
return new TableNotFoundException($message, $exception);
}
if (strpos($exception->getMessage(), 'already exists') !== false) {
return new TableExistsException($message, $exception);
}
if (strpos($exception->getMessage(), 'has no column named') !== false) {
return new InvalidFieldNameException($message, $exception);
}
if (strpos($exception->getMessage(), 'ambiguous column name') !== false) {
return new NonUniqueFieldNameException($message, $exception);
}
if (strpos($exception->getMessage(), 'syntax error') !== false) {
return new SyntaxErrorException($message, $exception);
}
if (strpos($exception->getMessage(), 'attempt to write a readonly database') !== false) {
return new ReadOnlyException($message, $exception);
}
if (strpos($exception->getMessage(), 'unable to open database file') !== false) {
return new ConnectionException($message, $exception);
}
return new DriverException($message, $exception);
}
/**
* {@inheritdoc}
*
* @deprecated Use Connection::getDatabase() instead.
*/
public function getDatabase(Connection $conn)
{
$params = $conn->getParams();
return $params['path'] ?? null;
}
/**
* {@inheritdoc}
*/
public function getDatabasePlatform()
{
return new SqlitePlatform();
}
/**
* {@inheritdoc}
*/
public function getSchemaManager(Connection $conn)
{
return new SqliteSchemaManager($conn);
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\ParameterType;
/**
* Connection interface.
* Driver connections must implement this interface.
*
* This resembles (a subset of) the PDO interface.
*/
interface Connection
{
/**
* Prepares a statement for execution and returns a Statement object.
*
* @param string $sql
*
* @return Statement
*/
public function prepare($sql);
/**
* Executes an SQL statement, returning a result set as a Statement object.
*
* @return Statement
*/
public function query();
/**
* Quotes a string for use in a query.
*
* @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.
*
* @param string $sql
*
* @return int|string
*/
public function exec($sql);
/**
* Returns the ID of the last inserted row or sequence value.
*
* @param string|null $name
*
* @return string|int|false
*/
public function lastInsertId($name = null);
/**
* Initiates a transaction.
*
* @return bool TRUE on success or FALSE on failure.
*/
public function beginTransaction();
/**
* Commits a transaction.
*
* @return bool TRUE on success or FALSE on failure.
*/
public function commit();
/**
* Rolls back the current transaction, as initiated by beginTransaction().
*
* @return bool TRUE on success or FALSE on failure.
*/
public function rollBack();
/**
* Returns the error code associated with the last operation on the database handle.
*
* @deprecated The error information is available via exceptions.
*
* @return string|null The error code, or null if no operation has been run on the database handle.
*/
public function errorCode();
/**
* Returns extended error information associated with the last operation on the database handle.
*
* @deprecated The error information is available via exceptions.
*
* @return mixed[]
*/
public function errorInfo();
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Doctrine\DBAL\Driver;
/**
* @deprecated Use {@link Exception} instead
*
* @psalm-immutable
*/
interface DriverException extends Exception
{
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Doctrine\DBAL\Driver\DrizzlePDOMySql;
use Doctrine\DBAL\Driver\PDOConnection;
use Doctrine\DBAL\ParameterType;
/**
* @deprecated
*/
class Connection extends PDOConnection
{
/**
* {@inheritdoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
if ($type === ParameterType::BOOLEAN) {
return $value ? 'true' : 'false';
}
return parent::quote($value, $type);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Doctrine\DBAL\Driver\DrizzlePDOMySql;
use Doctrine\DBAL\Platforms\DrizzlePlatform;
use Doctrine\DBAL\Schema\DrizzleSchemaManager;
/**
* Drizzle driver using PDO MySql.
*
* @deprecated
*/
class Driver extends \Doctrine\DBAL\Driver\PDOMySql\Driver
{
/**
* {@inheritdoc}
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
return new Connection(
$this->constructPdoDsn($params),
$username,
$password,
$driverOptions
);
}
/**
* {@inheritdoc}
*/
public function createDatabasePlatformForVersion($version)
{
return $this->getDatabasePlatform();
}
/**
* {@inheritdoc}
*
* @return DrizzlePlatform
*/
public function getDatabasePlatform()
{
return new DrizzlePlatform();
}
/**
* {@inheritdoc}
*
* @return DrizzleSchemaManager
*/
public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
{
return new DrizzleSchemaManager($conn);
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
return 'drizzle_pdo_mysql';
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver;
use Throwable;
/**
* @psalm-immutable
*/
interface Exception extends Throwable
{
/**
* Returns the driver specific error code if available.
*
* @deprecated Use {@link getCode()} or {@link getSQLState()} instead
*
* Returns null if no driver specific error code is available
* for the error raised by the driver.
*
* @return int|string|null
*/
public function getErrorCode();
/**
* 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();
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as TheDriverException;
use Doctrine\DBAL\Exception\DriverException;
/**
* Contract for a driver that is capable of converting DBAL driver exceptions into standardized DBAL driver exceptions.
*
* @deprecated
*/
interface ExceptionConverterDriver
{
/**
* Converts a given DBAL driver exception into a standardized DBAL driver exception.
*
* It evaluates the vendor specific error code and SQLSTATE and transforms
* it into a unified {@link DriverException} subclass.
*
* @deprecated
*
* @param string $message The DBAL exception message to use.
* @param TheDriverException $exception The DBAL driver exception to convert.
*
* @return DriverException An instance of one of the DriverException subclasses.
*/
public function convertException($message, TheDriverException $exception);
}

View File

@@ -0,0 +1,75 @@
<?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 array<int,array<int,mixed>>
*
* @throws Exception
*/
public static function fetchAllNumeric(Result $result): array
{
$rows = [];
while (($row = $result->fetchNumeric()) !== false) {
$rows[] = $row;
}
return $rows;
}
/**
* @return array<int,array<string,mixed>>
*
* @throws Exception
*/
public static function fetchAllAssociative(Result $result): array
{
$rows = [];
while (($row = $result->fetchAssociative()) !== false) {
$rows[] = $row;
}
return $rows;
}
/**
* @return array<int,mixed>
*
* @throws Exception
*/
public static function fetchFirstColumn(Result $result): array
{
$rows = [];
while (($row = $result->fetchOne()) !== false) {
$rows[] = $row;
}
return $rows;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
final class Connection extends DB2Connection
{
}

View File

@@ -0,0 +1,221 @@
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionError;
use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionFailed;
use Doctrine\DBAL\Driver\IBMDB2\Exception\PrepareFailed;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use stdClass;
use function assert;
use function db2_autocommit;
use function db2_commit;
use function db2_conn_error;
use function db2_conn_errormsg;
use function db2_connect;
use function db2_escape_string;
use function db2_exec;
use function db2_last_insert_id;
use function db2_num_rows;
use function db2_pconnect;
use function db2_prepare;
use function db2_rollback;
use function db2_server_info;
use function error_get_last;
use function func_get_args;
use function is_bool;
use const DB2_AUTOCOMMIT_OFF;
use const DB2_AUTOCOMMIT_ON;
/**
* @deprecated Use {@link Connection} instead
*/
class DB2Connection implements ConnectionInterface, ServerInfoAwareConnection
{
/** @var resource */
private $conn;
/**
* @internal The connection can be only instantiated by its driver.
*
* @param mixed[] $params
* @param string $username
* @param string $password
* @param mixed[] $driverOptions
*
* @throws DB2Exception
*/
public function __construct(array $params, $username, $password, $driverOptions = [])
{
$isPersistent = (isset($params['persistent']) && $params['persistent'] === true);
if ($isPersistent) {
$conn = db2_pconnect($params['dbname'], $username, $password, $driverOptions);
} else {
$conn = db2_connect($params['dbname'], $username, $password, $driverOptions);
}
if ($conn === false) {
throw ConnectionFailed::new();
}
$this->conn = $conn;
}
/**
* {@inheritdoc}
*/
public function getServerVersion()
{
$serverInfo = db2_server_info($this->conn);
assert($serverInfo instanceof stdClass);
return $serverInfo->DBMS_VER;
}
/**
* {@inheritdoc}
*/
public function requiresQueryForServerVersion()
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4114',
'ServerInfoAwareConnection::requiresQueryForServerVersion() is deprecated and removed in DBAL 3.'
);
return false;
}
/**
* {@inheritdoc}
*/
public function prepare($sql)
{
$stmt = @db2_prepare($this->conn, $sql);
if ($stmt === false) {
throw PrepareFailed::new(error_get_last());
}
return new Statement($stmt);
}
/**
* {@inheritdoc}
*/
public function query()
{
$args = func_get_args();
$sql = $args[0];
$stmt = $this->prepare($sql);
$stmt->execute();
return $stmt;
}
/**
* {@inheritdoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
$value = db2_escape_string($value);
if ($type === ParameterType::INTEGER) {
return $value;
}
return "'" . $value . "'";
}
/**
* {@inheritdoc}
*/
public function exec($sql)
{
$stmt = @db2_exec($this->conn, $sql);
if ($stmt === false) {
throw ConnectionError::new($this->conn);
}
return db2_num_rows($stmt);
}
/**
* {@inheritdoc}
*/
public function lastInsertId($name = null)
{
return db2_last_insert_id($this->conn);
}
/**
* {@inheritdoc}
*/
public function beginTransaction()
{
$result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_OFF);
assert(is_bool($result));
return $result;
}
/**
* {@inheritdoc}
*/
public function commit()
{
if (! db2_commit($this->conn)) {
throw ConnectionError::new($this->conn);
}
$result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_ON);
assert(is_bool($result));
return $result;
}
/**
* {@inheritdoc}
*/
public function rollBack()
{
if (! db2_rollback($this->conn)) {
throw ConnectionError::new($this->conn);
}
$result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_ON);
assert(is_bool($result));
return $result;
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorCode()
{
return db2_conn_error($this->conn);
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorInfo()
{
return [
0 => db2_conn_errormsg($this->conn),
1 => $this->errorCode(),
];
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\AbstractDB2Driver;
use Doctrine\Deprecations\Deprecation;
/**
* IBM DB2 Driver.
*
* @deprecated Use {@link Driver} instead
*/
class DB2Driver extends AbstractDB2Driver
{
/**
* {@inheritdoc}
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
$params['user'] = $username;
$params['password'] = $password;
$params['dbname'] = DataSourceName::fromConnectionParameters($params)->toString();
return new Connection(
$params,
(string) $username,
(string) $password,
$driverOptions
);
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/3580',
'Driver::getName() is deprecated'
);
return 'ibm_db2';
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\AbstractDriverException;
/**
* @deprecated Use {@link \Doctrine\DBAL\Driver\Exception} instead
*
* @psalm-immutable
*/
class DB2Exception extends AbstractDriverException
{
}

View File

@@ -0,0 +1,562 @@
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCopyStreamToStream;
use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCreateTemporaryFile;
use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotWriteToTemporaryFile;
use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\Driver\StatementIterator;
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
use IteratorAggregate;
use PDO;
use ReflectionClass;
use ReflectionObject;
use ReflectionProperty;
use ReturnTypeWillChange;
use stdClass;
use function array_change_key_case;
use function assert;
use function db2_bind_param;
use function db2_execute;
use function db2_fetch_array;
use function db2_fetch_assoc;
use function db2_fetch_both;
use function db2_fetch_object;
use function db2_free_result;
use function db2_num_fields;
use function db2_num_rows;
use function db2_stmt_error;
use function db2_stmt_errormsg;
use function error_get_last;
use function fclose;
use function func_get_args;
use function func_num_args;
use function fwrite;
use function gettype;
use function is_int;
use function is_object;
use function is_resource;
use function is_string;
use function ksort;
use function sprintf;
use function stream_copy_to_stream;
use function stream_get_meta_data;
use function strtolower;
use function tmpfile;
use const CASE_LOWER;
use const DB2_BINARY;
use const DB2_CHAR;
use const DB2_LONG;
use const DB2_PARAM_FILE;
use const DB2_PARAM_IN;
/**
* @deprecated Use {@link Statement} instead
*/
class DB2Statement implements IteratorAggregate, StatementInterface, Result
{
/** @var resource */
private $stmt;
/** @var mixed[] */
private $bindParam = [];
/**
* 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 mixed[][]
*/
private $lobs = [];
/** @var string Name of the default class to instantiate when fetching class instances. */
private $defaultFetchClass = '\stdClass';
/** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */
private $defaultFetchClassCtorArgs = [];
/** @var int */
private $defaultFetchMode = FetchMode::MIXED;
/**
* Indicates whether the statement is in the state when fetching results is possible
*
* @var bool
*/
private $result = false;
/**
* @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)
{
assert(is_int($param));
return $this->bindParam($param, $value, $type);
}
/**
* {@inheritdoc}
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
{
assert(is_int($param));
switch ($type) {
case ParameterType::INTEGER:
$this->bind($param, $variable, DB2_PARAM_IN, DB2_LONG);
break;
case ParameterType::LARGE_OBJECT:
if (isset($this->lobs[$param])) {
[, $handle] = $this->lobs[$param];
fclose($handle);
}
$handle = $this->createTemporaryFile();
$path = stream_get_meta_data($handle)['uri'];
$this->bind($param, $path, DB2_PARAM_FILE, DB2_BINARY);
$this->lobs[$param] = [&$variable, $handle];
break;
default:
$this->bind($param, $variable, DB2_PARAM_IN, DB2_CHAR);
break;
}
return true;
}
/**
* @param int $position Parameter position
* @param mixed $variable
*
* @throws DB2Exception
*/
private function bind($position, &$variable, int $parameterType, int $dataType): void
{
$this->bindParam[$position] =& $variable;
if (! db2_bind_param($this->stmt, $position, 'variable', $parameterType, $dataType)) {
throw StatementError::new($this->stmt);
}
}
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*/
public function closeCursor()
{
$this->bindParam = [];
if (! db2_free_result($this->stmt)) {
return false;
}
$this->result = false;
return true;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return db2_num_fields($this->stmt) ?: 0;
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorCode()
{
return db2_stmt_error();
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorInfo()
{
return [
db2_stmt_errormsg(),
db2_stmt_error(),
];
}
/**
* {@inheritdoc}
*/
public function execute($params = null)
{
if ($params === null) {
ksort($this->bindParam);
$params = [];
foreach ($this->bindParam as $column => $value) {
$params[] = $value;
}
}
foreach ($this->lobs as [$source, $target]) {
if (is_resource($source)) {
$this->copyStreamToStream($source, $target);
continue;
}
$this->writeStringToStream($source, $target);
}
$retval = db2_execute($this->stmt, $params);
foreach ($this->lobs as [, $handle]) {
fclose($handle);
}
$this->lobs = [];
if ($retval === false) {
throw StatementError::new($this->stmt);
}
$this->result = true;
return $retval;
}
/**
* {@inheritdoc}
*
* @deprecated Use one of the fetch- or iterate-related methods.
*/
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
{
$this->defaultFetchMode = $fetchMode;
$this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass;
$this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
return true;
}
/**
* {@inheritdoc}
*
* @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead.
*/
#[ReturnTypeWillChange]
public function getIterator()
{
return new StatementIterator($this);
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
*/
public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
{
// do not try fetching from the statement if it's not expected to contain result
// in order to prevent exceptional situation
if (! $this->result) {
return false;
}
$fetchMode = $fetchMode ?: $this->defaultFetchMode;
switch ($fetchMode) {
case FetchMode::COLUMN:
return $this->fetchColumn();
case FetchMode::MIXED:
return db2_fetch_both($this->stmt);
case FetchMode::ASSOCIATIVE:
return db2_fetch_assoc($this->stmt);
case FetchMode::CUSTOM_OBJECT:
$className = $this->defaultFetchClass;
$ctorArgs = $this->defaultFetchClassCtorArgs;
if (func_num_args() >= 2) {
$args = func_get_args();
$className = $args[1];
$ctorArgs = $args[2] ?? [];
}
$result = db2_fetch_object($this->stmt);
if ($result instanceof stdClass) {
$result = $this->castObject($result, $className, $ctorArgs);
}
return $result;
case FetchMode::NUMERIC:
return db2_fetch_array($this->stmt);
case FetchMode::STANDARD_OBJECT:
return db2_fetch_object($this->stmt);
default:
throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
}
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
*/
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
{
$rows = [];
switch ($fetchMode) {
case FetchMode::CUSTOM_OBJECT:
while (($row = $this->fetch(...func_get_args())) !== false) {
$rows[] = $row;
}
break;
case FetchMode::COLUMN:
while (($row = $this->fetchColumn()) !== false) {
$rows[] = $row;
}
break;
default:
while (($row = $this->fetch($fetchMode)) !== false) {
$rows[] = $row;
}
}
return $rows;
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchOne() instead.
*/
public function fetchColumn($columnIndex = 0)
{
$row = $this->fetch(FetchMode::NUMERIC);
if ($row === false) {
return false;
}
return $row[$columnIndex] ?? null;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
if (! $this->result) {
return false;
}
return db2_fetch_array($this->stmt);
}
/**
* {@inheritdoc}
*/
public function fetchAssociative()
{
// do not try fetching from the statement if it's not expected to contain the result
// in order to prevent exceptional situation
if (! $this->result) {
return false;
}
return db2_fetch_assoc($this->stmt);
}
/**
* {@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);
}
/**
* {@inheritdoc}
*/
public function rowCount()
{
return @db2_num_rows($this->stmt) ? : 0;
}
public function free(): void
{
$this->bindParam = [];
db2_free_result($this->stmt);
$this->result = false;
}
/**
* Casts a stdClass object to the given class name mapping its' properties.
*
* @param stdClass $sourceObject Object to cast from.
* @param class-string|object $destinationClass Name of the class or class instance to cast to.
* @param mixed[] $ctorArgs Arguments to use for constructing the destination class instance.
*
* @return object
*
* @throws DB2Exception
*/
private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
{
if (! is_string($destinationClass)) {
if (! is_object($destinationClass)) {
throw new DB2Exception(sprintf(
'Destination class has to be of type string or object, %s given.',
gettype($destinationClass)
));
}
} else {
$destinationClass = new ReflectionClass($destinationClass);
$destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
}
$sourceReflection = new ReflectionObject($sourceObject);
$destinationClassReflection = new ReflectionObject($destinationClass);
/** @var ReflectionProperty[] $destinationProperties */
$destinationProperties = array_change_key_case($destinationClassReflection->getProperties(), CASE_LOWER);
foreach ($sourceReflection->getProperties() as $sourceProperty) {
$sourceProperty->setAccessible(true);
$name = $sourceProperty->getName();
$value = $sourceProperty->getValue($sourceObject);
// Try to find a case-matching property.
if ($destinationClassReflection->hasProperty($name)) {
$destinationProperty = $destinationClassReflection->getProperty($name);
$destinationProperty->setAccessible(true);
$destinationProperty->setValue($destinationClass, $value);
continue;
}
$name = strtolower($name);
// Try to find a property without matching case.
// Fallback for the driver returning either all uppercase or all lowercase column names.
if (isset($destinationProperties[$name])) {
$destinationProperty = $destinationProperties[$name];
$destinationProperty->setAccessible(true);
$destinationProperty->setValue($destinationClass, $value);
continue;
}
$destinationClass->$name = $value;
}
return $destinationClass;
}
/**
* @return resource
*
* @throws DB2Exception
*/
private function createTemporaryFile()
{
$handle = @tmpfile();
if ($handle === false) {
throw CannotCreateTemporaryFile::new(error_get_last());
}
return $handle;
}
/**
* @param resource $source
* @param resource $target
*
* @throws DB2Exception
*/
private function copyStreamToStream($source, $target): void
{
if (@stream_copy_to_stream($source, $target) === false) {
throw CannotCopyStreamToStream::new(error_get_last());
}
}
/**
* @param resource $target
*
* @throws DB2Exception
*/
private function writeStringToStream(string $string, $target): void
{
if (@fwrite($target, $string) === false) {
throw CannotWriteToTemporaryFile::new(error_get_last());
}
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2;
use function implode;
use function sprintf;
use function strpos;
/**
* IBM DB2 DSN
*/
final class DataSourceName
{
/** @var string */
private $string;
private function __construct(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(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(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);
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2;
final class Driver extends DB2Driver
{
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\IBMDB2\DB2Exception;
/**
* @internal
*
* @psalm-immutable
*/
final class CannotCopyStreamToStream extends DB2Exception
{
/**
* @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);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\IBMDB2\DB2Exception;
/**
* @internal
*
* @psalm-immutable
*/
final class CannotCreateTemporaryFile extends DB2Exception
{
/**
* @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);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\IBMDB2\DB2Exception;
/**
* @internal
*
* @psalm-immutable
*/
final class CannotWriteToTemporaryFile extends DB2Exception
{
/**
* @psalm-param array{message: string}|null $error
*/
public static function new(?array $error): self
{
$message = 'Could not write string to temporary file';
if ($error !== null) {
$message .= ': ' . $error['message'];
}
return new self($message);
}
}

View File

@@ -0,0 +1,26 @@
<?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
{
return new self(db2_conn_errormsg($connection), db2_conn_error($connection));
}
}

View File

@@ -0,0 +1,23 @@
<?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
{
return new self(db2_conn_errormsg(), db2_conn_error());
}
}

View File

@@ -0,0 +1,27 @@
<?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']);
}
}

View File

@@ -0,0 +1,26 @@
<?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 $statement
*/
public static function new($statement): self
{
return new self(db2_stmt_errormsg($statement), db2_stmt_error($statement));
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
final class Statement extends DB2Statement
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
final class Connection extends MysqliConnection
{
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\AbstractMySQLDriver;
use Doctrine\DBAL\Exception;
use Doctrine\Deprecations\Deprecation;
class Driver extends AbstractMySQLDriver
{
/**
* {@inheritdoc}
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
try {
return new Connection($params, (string) $username, (string) $password, $driverOptions);
} catch (MysqliException $e) {
throw Exception::driverException($this, $e);
}
}
/**
* {@inheritdoc}
*/
public function getName()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/3580',
'Driver::getName() is deprecated'
);
return 'mysqli';
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use mysqli;
use mysqli_sql_exception;
use ReflectionProperty;
/**
* @internal
*
* @psalm-immutable
*/
final class ConnectionError extends MysqliException
{
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), $exception->getCode(), $exception);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use mysqli;
use mysqli_sql_exception;
use ReflectionProperty;
use function assert;
/**
* @internal
*
* @psalm-immutable
*/
final class ConnectionFailed extends MysqliException
{
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), $exception->getCode(), $exception);
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class FailedReadingStreamOffset extends MysqliException
{
public static function new(int $offset): self
{
return new self(sprintf('Failed reading the stream resource for parameter offset %d.', $offset));
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class InvalidOption extends MysqliException
{
/**
* @param mixed $option
* @param mixed $value
*/
public static function fromOption($option, $value): self
{
return new self(
sprintf('Failed to set option %d with value "%s"', $option, $value)
);
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use mysqli_sql_exception;
use mysqli_stmt;
use ReflectionProperty;
/**
* @internal
*
* @psalm-immutable
*/
final class StatementError extends MysqliException
{
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), $exception->getCode(), $exception);
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class UnknownType extends MysqliException
{
/**
* @param mixed $type
*/
public static function new($type): self
{
return new self(sprintf('Unknown type, %d given.', $type));
}
}

View File

@@ -0,0 +1,345 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError;
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionFailed;
use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidOption;
use Doctrine\DBAL\Driver\PingableConnection;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use mysqli;
use mysqli_sql_exception;
use function assert;
use function floor;
use function func_get_args;
use function in_array;
use function ini_get;
use function mysqli_errno;
use function mysqli_error;
use function mysqli_init;
use function mysqli_options;
use function sprintf;
use function stripos;
use const MYSQLI_INIT_COMMAND;
use const MYSQLI_OPT_CONNECT_TIMEOUT;
use const MYSQLI_OPT_LOCAL_INFILE;
use const MYSQLI_OPT_READ_TIMEOUT;
use const MYSQLI_READ_DEFAULT_FILE;
use const MYSQLI_READ_DEFAULT_GROUP;
use const MYSQLI_SERVER_PUBLIC_KEY;
/**
* @deprecated Use {@link Connection} instead
*/
class MysqliConnection implements ConnectionInterface, PingableConnection, ServerInfoAwareConnection
{
/**
* Name of the option to set connection flags
*/
public const OPTION_FLAGS = 'flags';
/** @var mysqli */
private $conn;
/**
* @internal The connection can be only instantiated by its driver.
*
* @param mixed[] $params
* @param string $username
* @param string $password
* @param mixed[] $driverOptions
*
* @throws MysqliException
*/
public function __construct(array $params, $username, $password, array $driverOptions = [])
{
$port = $params['port'] ?? ini_get('mysqli.default_port');
// Fallback to default MySQL port if not given.
if (! $port) {
$port = 3306;
}
$socket = $params['unix_socket'] ?? ini_get('mysqli.default_socket');
$dbname = $params['dbname'] ?? null;
$flags = $driverOptions[static::OPTION_FLAGS] ?? 0;
$conn = mysqli_init();
assert($conn !== false);
$this->conn = $conn;
$this->setSecureConnection($params);
$this->setDriverOptions($driverOptions);
try {
$success = @$this->conn
->real_connect($params['host'], $username, $password, $dbname, $port, $socket, $flags);
} catch (mysqli_sql_exception $e) {
throw ConnectionFailed::upcast($e);
}
if (! $success) {
throw ConnectionFailed::new($this->conn);
}
if (! isset($params['charset'])) {
return;
}
$this->conn->set_charset($params['charset']);
}
/**
* Retrieves mysqli native resource handle.
*
* Could be used if part of your application is not using DBAL.
*
* @return mysqli
*/
public function getWrappedResourceHandle()
{
return $this->conn;
}
/**
* {@inheritdoc}
*
* The server version detection includes a special case for MariaDB
* to support '5.5.5-' prefixed versions introduced in Maria 10+
*
* @link https://jira.mariadb.org/browse/MDEV-4088
*/
public function getServerVersion()
{
$serverInfos = $this->conn->get_server_info();
if (stripos($serverInfos, 'mariadb') !== false) {
return $serverInfos;
}
$majorVersion = floor($this->conn->server_version / 10000);
$minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100);
$patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100);
return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
}
/**
* {@inheritdoc}
*/
public function requiresQueryForServerVersion()
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4114',
'ServerInfoAwareConnection::requiresQueryForServerVersion() is deprecated and removed in DBAL 3.'
);
return false;
}
/**
* {@inheritdoc}
*/
public function prepare($sql)
{
return new Statement($this->conn, $sql);
}
/**
* {@inheritdoc}
*/
public function query()
{
$args = func_get_args();
$sql = $args[0];
$stmt = $this->prepare($sql);
$stmt->execute();
return $stmt;
}
/**
* {@inheritdoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
return "'" . $this->conn->escape_string($value) . "'";
}
/**
* {@inheritdoc}
*/
public function exec($sql)
{
try {
$result = $this->conn->query($sql);
} catch (mysqli_sql_exception $e) {
throw ConnectionError::upcast($e);
}
if ($result === false) {
throw ConnectionError::new($this->conn);
}
return $this->conn->affected_rows;
}
/**
* {@inheritdoc}
*/
public function lastInsertId($name = null)
{
return $this->conn->insert_id;
}
/**
* {@inheritdoc}
*/
public function beginTransaction()
{
$this->conn->query('START TRANSACTION');
return true;
}
/**
* {@inheritdoc}
*/
public function commit()
{
try {
return $this->conn->commit();
} catch (mysqli_sql_exception $e) {
return false;
}
}
/**
* {@inheritdoc}non-PHPdoc)
*/
public function rollBack()
{
try {
return $this->conn->rollback();
} catch (mysqli_sql_exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*
* @return int
*/
public function errorCode()
{
return $this->conn->errno;
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*
* @return string
*/
public function errorInfo()
{
return $this->conn->error;
}
/**
* Apply the driver options to the connection.
*
* @param mixed[] $driverOptions
*
* @throws MysqliException When one of of the options is not supported.
* @throws MysqliException When applying doesn't work - e.g. due to incorrect value.
*/
private function setDriverOptions(array $driverOptions = []): void
{
$supportedDriverOptions = [
MYSQLI_OPT_CONNECT_TIMEOUT,
MYSQLI_OPT_LOCAL_INFILE,
MYSQLI_OPT_READ_TIMEOUT,
MYSQLI_INIT_COMMAND,
MYSQLI_READ_DEFAULT_FILE,
MYSQLI_READ_DEFAULT_GROUP,
MYSQLI_SERVER_PUBLIC_KEY,
];
$exceptionMsg = "%s option '%s' with value '%s'";
foreach ($driverOptions as $option => $value) {
if ($option === static::OPTION_FLAGS) {
continue;
}
if (! in_array($option, $supportedDriverOptions, true)) {
throw InvalidOption::fromOption($option, $value);
}
if (@mysqli_options($this->conn, $option, $value)) {
continue;
}
$msg = sprintf($exceptionMsg, 'Failed to set', $option, $value);
$msg .= sprintf(', error: %s (%d)', mysqli_error($this->conn), mysqli_errno($this->conn));
throw new MysqliException(
$msg,
$this->conn->sqlstate,
$this->conn->errno
);
}
}
/**
* Pings the server and re-connects when `mysqli.reconnect = 1`
*
* @deprecated
*
* @return bool
*/
public function ping()
{
return $this->conn->ping();
}
/**
* Establish a secure connection
*
* @param array<string,string> $params
*
* @throws MysqliException
*/
private function setSecureConnection(array $params): void
{
if (
! isset($params['ssl_key']) &&
! isset($params['ssl_cert']) &&
! isset($params['ssl_ca']) &&
! isset($params['ssl_capath']) &&
! isset($params['ssl_cipher'])
) {
return;
}
$this->conn->ssl_set(
$params['ssl_key'] ?? '',
$params['ssl_cert'] ?? '',
$params['ssl_ca'] ?? '',
$params['ssl_capath'] ?? '',
$params['ssl_cipher'] ?? ''
);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\AbstractDriverException;
/**
* @deprecated Use {@link \Doctrine\DBAL\Driver\Exception} instead
*
* @psalm-immutable
*/
class MysqliException extends AbstractDriverException
{
}

View File

@@ -0,0 +1,583 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError;
use Doctrine\DBAL\Driver\Mysqli\Exception\FailedReadingStreamOffset;
use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError;
use Doctrine\DBAL\Driver\Mysqli\Exception\UnknownType;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\Driver\StatementIterator;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
use IteratorAggregate;
use mysqli;
use mysqli_sql_exception;
use mysqli_stmt;
use PDO;
use ReturnTypeWillChange;
use function array_combine;
use function array_fill;
use function assert;
use function count;
use function feof;
use function fread;
use function get_resource_type;
use function is_array;
use function is_int;
use function is_resource;
use function sprintf;
use function str_repeat;
/**
* @deprecated Use {@link Statement} instead
*/
class MysqliStatement implements IteratorAggregate, StatementInterface, Result
{
/** @var string[] */
protected static $_paramTypeMap = [
ParameterType::ASCII => 's',
ParameterType::STRING => 's',
ParameterType::BINARY => 's',
ParameterType::BOOLEAN => 'i',
ParameterType::NULL => 's',
ParameterType::INTEGER => 'i',
ParameterType::LARGE_OBJECT => 'b',
];
/** @var mysqli */
protected $_conn;
/** @var mysqli_stmt */
protected $_stmt;
/** @var string[]|false|null */
protected $_columnNames;
/** @var mixed[] */
protected $_rowBindedValues = [];
/** @var mixed[] */
protected $_bindedValues;
/** @var string */
protected $types;
/**
* Contains ref values for bindValue().
*
* @var mixed[]
*/
protected $_values = [];
/** @var int */
protected $_defaultFetchMode = FetchMode::MIXED;
/**
* Indicates whether the statement is in the state when fetching results is possible
*
* @var bool
*/
private $result = false;
/**
* @internal The statement can be only instantiated by its driver connection.
*
* @param string $prepareString
*
* @throws MysqliException
*/
public function __construct(mysqli $conn, $prepareString)
{
$this->_conn = $conn;
try {
$stmt = $conn->prepare($prepareString);
} catch (mysqli_sql_exception $e) {
throw ConnectionError::upcast($e);
}
if ($stmt === false) {
throw ConnectionError::new($this->_conn);
}
$this->_stmt = $stmt;
$paramCount = $this->_stmt->param_count;
$this->types = str_repeat('s', $paramCount);
$this->_bindedValues = array_fill(1, $paramCount, null);
}
/**
* {@inheritdoc}
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
{
assert(is_int($param));
if (! isset(self::$_paramTypeMap[$type])) {
throw UnknownType::new($type);
}
$this->_bindedValues[$param] =& $variable;
$this->types[$param - 1] = self::$_paramTypeMap[$type];
return true;
}
/**
* {@inheritdoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
assert(is_int($param));
if (! isset(self::$_paramTypeMap[$type])) {
throw UnknownType::new($type);
}
$this->_values[$param] = $value;
$this->_bindedValues[$param] =& $this->_values[$param];
$this->types[$param - 1] = self::$_paramTypeMap[$type];
return true;
}
/**
* {@inheritdoc}
*/
public function execute($params = null)
{
if ($params !== null && count($params) > 0) {
if (! $this->bindUntypedValues($params)) {
throw StatementError::new($this->_stmt);
}
} elseif (count($this->_bindedValues) > 0) {
$this->bindTypedParameters();
}
try {
$result = $this->_stmt->execute();
} catch (mysqli_sql_exception $e) {
throw StatementError::upcast($e);
}
if (! $result) {
throw StatementError::new($this->_stmt);
}
if ($this->_columnNames === null) {
$meta = $this->_stmt->result_metadata();
if ($meta !== false) {
$fields = $meta->fetch_fields();
assert(is_array($fields));
$columnNames = [];
foreach ($fields as $col) {
$columnNames[] = $col->name;
}
$meta->free();
$this->_columnNames = $columnNames;
} else {
$this->_columnNames = false;
}
}
if ($this->_columnNames !== false) {
// 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->_stmt->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 closeCursor() has been
// previously called on the statement, 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->_rowBindedValues = array_fill(0, count($this->_columnNames), null);
$refs = [];
foreach ($this->_rowBindedValues as $key => &$value) {
$refs[$key] =& $value;
}
if (! $this->_stmt->bind_result(...$refs)) {
throw StatementError::new($this->_stmt);
}
}
$this->result = true;
return true;
}
/**
* Binds parameters with known types previously bound to the statement
*/
private function bindTypedParameters(): void
{
$streams = $values = [];
$types = $this->types;
foreach ($this->_bindedValues as $parameter => $value) {
assert(is_int($parameter));
if (! isset($types[$parameter - 1])) {
$types[$parameter - 1] = static::$_paramTypeMap[ParameterType::STRING];
}
if ($types[$parameter - 1] === static::$_paramTypeMap[ParameterType::LARGE_OBJECT]) {
if (is_resource($value)) {
if (get_resource_type($value) !== 'stream') {
throw new InvalidArgumentException(
'Resources passed with the LARGE_OBJECT parameter type must be stream resources.'
);
}
$streams[$parameter] = $value;
$values[$parameter] = null;
continue;
}
$types[$parameter - 1] = static::$_paramTypeMap[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 MysqliException
*/
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
*
* @return bool
*/
private function bindUntypedValues(array $values)
{
$params = [];
$types = str_repeat('s', count($values));
foreach ($values as &$v) {
$params[] =& $v;
}
return $this->_stmt->bind_param($types, ...$params);
}
/**
* @return mixed[]|false|null
*
* @throws StatementError
*/
private function _fetch()
{
try {
$ret = $this->_stmt->fetch();
} catch (mysqli_sql_exception $e) {
throw StatementError::upcast($e);
}
if ($ret === true) {
$values = [];
foreach ($this->_rowBindedValues as $v) {
$values[] = $v;
}
return $values;
}
return $ret;
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
*/
public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
{
// do not try fetching from the statement if it's not expected to contain result
// in order to prevent exceptional situation
if (! $this->result) {
return false;
}
$fetchMode = $fetchMode ?: $this->_defaultFetchMode;
if ($fetchMode === FetchMode::COLUMN) {
return $this->fetchColumn();
}
$values = $this->_fetch();
if ($values === null) {
return false;
}
if ($values === false) {
throw StatementError::new($this->_stmt);
}
if ($fetchMode === FetchMode::NUMERIC) {
return $values;
}
assert(is_array($this->_columnNames));
$assoc = array_combine($this->_columnNames, $values);
assert(is_array($assoc));
switch ($fetchMode) {
case FetchMode::ASSOCIATIVE:
return $assoc;
case FetchMode::MIXED:
return $assoc + $values;
case FetchMode::STANDARD_OBJECT:
return (object) $assoc;
default:
throw new MysqliException(sprintf("Unknown fetch type '%s'", $fetchMode));
}
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
*/
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
{
$fetchMode = $fetchMode ?: $this->_defaultFetchMode;
$rows = [];
if ($fetchMode === FetchMode::COLUMN) {
while (($row = $this->fetchColumn()) !== false) {
$rows[] = $row;
}
} else {
while (($row = $this->fetch($fetchMode)) !== false) {
$rows[] = $row;
}
}
return $rows;
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchOne() instead.
*/
public function fetchColumn($columnIndex = 0)
{
$row = $this->fetch(FetchMode::NUMERIC);
if ($row === false) {
return false;
}
return $row[$columnIndex] ?? null;
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function fetchNumeric()
{
// do not try fetching from the statement if it's not expected to contain the result
// in order to prevent exceptional situation
if (! $this->result) {
return false;
}
$values = $this->_fetch();
if ($values === null) {
return false;
}
if ($values === false) {
throw StatementError::new($this->_stmt);
}
return $values;
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
$values = $this->fetchNumeric();
if ($values === false) {
return false;
}
assert(is_array($this->_columnNames));
$row = array_combine($this->_columnNames, $values);
assert(is_array($row));
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);
}
/**
* {@inheritdoc}
*/
public function errorCode()
{
return $this->_stmt->errno;
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*
* @return string
*/
public function errorInfo()
{
return $this->_stmt->error;
}
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*/
public function closeCursor()
{
$this->free();
return true;
}
/**
* {@inheritdoc}
*/
public function rowCount()
{
if ($this->_columnNames === false) {
return $this->_stmt->affected_rows;
}
return $this->_stmt->num_rows;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return $this->_stmt->field_count;
}
public function free(): void
{
$this->_stmt->free_result();
$this->result = false;
}
/**
* {@inheritdoc}
*
* @deprecated Use one of the fetch- or iterate-related methods.
*/
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
{
$this->_defaultFetchMode = $fetchMode;
return true;
}
/**
* {@inheritdoc}
*
* @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead.
*/
#[ReturnTypeWillChange]
public function getIterator()
{
return new StatementIterator($this);
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
final class Statement extends MysqliStatement
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Doctrine\DBAL\Driver\OCI8;
final class Connection extends OCI8Connection
{
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\AbstractOracleDriver;
use Doctrine\DBAL\Exception;
use Doctrine\Deprecations\Deprecation;
use const OCI_NO_AUTO_COMMIT;
/**
* A Doctrine DBAL driver for the Oracle OCI8 PHP extensions.
*/
class Driver extends AbstractOracleDriver
{
/**
* {@inheritdoc}
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
try {
return new Connection(
(string) $username,
(string) $password,
$this->_constructDsn($params),
$params['charset'] ?? '',
$params['sessionMode'] ?? OCI_NO_AUTO_COMMIT,
$params['persistent'] ?? false
);
} catch (OCI8Exception $e) {
throw Exception::driverException($this, $e);
}
}
/**
* Constructs the Oracle DSN.
*
* @param mixed[] $params
*
* @return string The DSN.
*/
protected function _constructDsn(array $params)
{
return $this->getEasyConnectString($params);
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/3580',
'Driver::getName() is deprecated'
);
return 'oci8';
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\OCI8\OCI8Exception;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class NonTerminatedStringLiteral extends OCI8Exception
{
public static function new(int $offset): self
{
return new self(
sprintf(
'The statement contains non-terminated string literal starting at offset %d.',
$offset
)
);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\OCI8\OCI8Exception;
/**
* @internal
*
* @psalm-immutable
*/
final class SequenceDoesNotExist extends OCI8Exception
{
public static function new(): self
{
return new self('lastInsertId failed: Query was executed but no result was returned.');
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\OCI8\OCI8Exception;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class UnknownParameterIndex extends OCI8Exception
{
public static function new(int $index): self
{
return new self(
sprintf('Could not find variable mapping with index %d, in the SQL statement', $index)
);
}
}

View File

@@ -0,0 +1,268 @@
<?php
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
use Doctrine\DBAL\Driver\OCI8\Exception\SequenceDoesNotExist;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use UnexpectedValueException;
use function addcslashes;
use function func_get_args;
use function is_float;
use function is_int;
use function oci_commit;
use function oci_connect;
use function oci_error;
use function oci_pconnect;
use function oci_rollback;
use function oci_server_version;
use function preg_match;
use function sprintf;
use function str_replace;
use const OCI_COMMIT_ON_SUCCESS;
use const OCI_NO_AUTO_COMMIT;
/**
* OCI8 implementation of the Connection interface.
*
* @deprecated Use {@link Connection} instead
*/
class OCI8Connection implements ConnectionInterface, ServerInfoAwareConnection
{
/** @var resource */
protected $dbh;
/** @var int */
protected $executeMode = OCI_COMMIT_ON_SUCCESS;
/**
* Creates a Connection to an Oracle Database using oci8 extension.
*
* @internal The connection can be only instantiated by its driver.
*
* @param string $username
* @param string $password
* @param string $db
* @param string $charset
* @param int $sessionMode
* @param bool $persistent
*
* @throws OCI8Exception
*/
public function __construct(
$username,
$password,
$db,
$charset = '',
$sessionMode = OCI_NO_AUTO_COMMIT,
$persistent = false
) {
$dbh = $persistent
? @oci_pconnect($username, $password, $db, $charset, $sessionMode)
: @oci_connect($username, $password, $db, $charset, $sessionMode);
if ($dbh === false) {
throw OCI8Exception::fromErrorInfo(oci_error());
}
$this->dbh = $dbh;
}
/**
* {@inheritdoc}
*
* @throws UnexpectedValueException If the version string returned by the database server
* does not contain a parsable version number.
*/
public function getServerVersion()
{
$version = oci_server_version($this->dbh);
if ($version === false) {
throw OCI8Exception::fromErrorInfo(oci_error($this->dbh));
}
if (! preg_match('/\s+(\d+\.\d+\.\d+\.\d+\.\d+)\s+/', $version, $matches)) {
throw new UnexpectedValueException(
sprintf(
'Unexpected database version string "%s". Cannot parse an appropriate version number from it. ' .
'Please report this database version string to the Doctrine team.',
$version
)
);
}
return $matches[1];
}
/**
* {@inheritdoc}
*/
public function requiresQueryForServerVersion()
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4114',
'ServerInfoAwareConnection::requiresQueryForServerVersion() is deprecated and removed in DBAL 3.'
);
return false;
}
/**
* {@inheritdoc}
*/
public function prepare($sql)
{
return new Statement($this->dbh, $sql, $this);
}
/**
* {@inheritdoc}
*/
public function query()
{
$args = func_get_args();
$sql = $args[0];
//$fetchMode = $args[1];
$stmt = $this->prepare($sql);
$stmt->execute();
return $stmt;
}
/**
* {@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") . "'";
}
/**
* {@inheritdoc}
*/
public function exec($sql)
{
$stmt = $this->prepare($sql);
$stmt->execute();
return $stmt->rowCount();
}
/**
* {@inheritdoc}
*
* @param string|null $name
*
* @return int|false
*/
public function lastInsertId($name = null)
{
if ($name === null) {
return false;
}
$sql = 'SELECT ' . $name . '.CURRVAL FROM DUAL';
$stmt = $this->query($sql);
$result = $stmt->fetchColumn();
if ($result === false) {
throw SequenceDoesNotExist::new();
}
return (int) $result;
}
/**
* Returns the current execution mode.
*
* @internal
*
* @return int
*/
public function getExecuteMode()
{
return $this->executeMode;
}
/**
* {@inheritdoc}
*/
public function beginTransaction()
{
$this->executeMode = OCI_NO_AUTO_COMMIT;
return true;
}
/**
* {@inheritdoc}
*/
public function commit()
{
if (! oci_commit($this->dbh)) {
throw OCI8Exception::fromErrorInfo($this->errorInfo());
}
$this->executeMode = OCI_COMMIT_ON_SUCCESS;
return true;
}
/**
* {@inheritdoc}
*/
public function rollBack()
{
if (! oci_rollback($this->dbh)) {
throw OCI8Exception::fromErrorInfo($this->errorInfo());
}
$this->executeMode = OCI_COMMIT_ON_SUCCESS;
return true;
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorCode()
{
$error = oci_error($this->dbh);
if ($error !== false) {
return $error['code'];
}
return null;
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorInfo()
{
$error = oci_error($this->dbh);
if ($error === false) {
return [];
}
return $error;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\AbstractDriverException;
/**
* @deprecated Use {@link \Doctrine\DBAL\Driver\Exception} instead
*
* @psalm-immutable
*/
class OCI8Exception extends AbstractDriverException
{
/**
* @param mixed[]|false $error
*
* @return OCI8Exception
*/
public static function fromErrorInfo($error)
{
if ($error === false) {
return new self('Database error occurred but no error information was retrieved from the driver.');
}
return new self($error['message'], null, $error['code']);
}
}

View File

@@ -0,0 +1,655 @@
<?php
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\OCI8\Exception\NonTerminatedStringLiteral;
use Doctrine\DBAL\Driver\OCI8\Exception\UnknownParameterIndex;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\Driver\StatementIterator;
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
use InvalidArgumentException;
use IteratorAggregate;
use PDO;
use ReturnTypeWillChange;
use function array_key_exists;
use function assert;
use function count;
use function implode;
use function is_int;
use function is_resource;
use function oci_bind_by_name;
use function oci_cancel;
use function oci_error;
use function oci_execute;
use function oci_fetch_all;
use function oci_fetch_array;
use function oci_fetch_object;
use function oci_new_descriptor;
use function oci_num_fields;
use function oci_num_rows;
use function oci_parse;
use function preg_match;
use function preg_quote;
use function substr;
use const OCI_ASSOC;
use const OCI_B_BIN;
use const OCI_B_BLOB;
use const OCI_BOTH;
use const OCI_D_LOB;
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;
use const OCI_TEMP_BLOB;
use const PREG_OFFSET_CAPTURE;
use const SQLT_CHR;
/**
* The OCI8 implementation of the Statement interface.
*
* @deprecated Use {@link Statement} instead
*/
class OCI8Statement implements IteratorAggregate, StatementInterface, Result
{
/** @var resource */
protected $_dbh;
/** @var resource */
protected $_sth;
/** @var OCI8Connection */
protected $_conn;
/**
* @deprecated
*
* @var string
*/
protected static $_PARAM = ':param';
/** @var int[] */
protected static $fetchModeMap = [
FetchMode::MIXED => OCI_BOTH,
FetchMode::ASSOCIATIVE => OCI_ASSOC,
FetchMode::NUMERIC => OCI_NUM,
FetchMode::COLUMN => OCI_NUM,
];
/** @var int */
protected $_defaultFetchMode = FetchMode::MIXED;
/** @var string[] */
protected $_paramMap = [];
/**
* Holds references to bound parameter values.
*
* This is a new requirement for PHP7's oci8 extension that prevents bound values from being garbage collected.
*
* @var mixed[]
*/
private $boundValues = [];
/**
* Indicates whether the statement is in the state when fetching results is possible
*
* @var bool
*/
private $result = false;
/**
* Creates a new OCI8Statement that uses the given connection handle and SQL statement.
*
* @internal The statement can be only instantiated by its driver connection.
*
* @param resource $dbh The connection handle.
* @param string $query The SQL query.
*/
public function __construct($dbh, $query, OCI8Connection $conn)
{
[$query, $paramMap] = self::convertPositionalToNamedPlaceholders($query);
$stmt = oci_parse($dbh, $query);
assert(is_resource($stmt));
$this->_sth = $stmt;
$this->_dbh = $dbh;
$this->_paramMap = $paramMap;
$this->_conn = $conn;
}
/**
* Converts positional (?) into named placeholders (:param<num>).
*
* Oracle does not support positional parameters, hence this method converts all
* positional parameters into artificially named parameters. Note that this conversion
* is not perfect. All question marks (?) in the original statement are treated as
* placeholders and converted to a named parameter.
*
* The algorithm uses a state machine with two possible states: InLiteral and NotInLiteral.
* Question marks inside literal strings are therefore handled correctly by this method.
* This comes at a cost, the whole sql statement has to be looped over.
*
* @internal
*
* @param string $statement The SQL statement to convert.
*
* @return mixed[] [0] => the statement value (string), [1] => the paramMap value (array).
*
* @throws OCI8Exception
*
* @todo extract into utility class in Doctrine\DBAL\Util namespace
* @todo review and test for lost spaces. we experienced missing spaces with oci8 in some sql statements.
*/
public static function convertPositionalToNamedPlaceholders($statement)
{
$fragmentOffset = $tokenOffset = 0;
$fragments = $paramMap = [];
$currentLiteralDelimiter = null;
do {
if (! $currentLiteralDelimiter) {
$result = self::findPlaceholderOrOpeningQuote(
$statement,
$tokenOffset,
$fragmentOffset,
$fragments,
$currentLiteralDelimiter,
$paramMap
);
} else {
$result = self::findClosingQuote($statement, $tokenOffset, $currentLiteralDelimiter);
}
} while ($result);
if ($currentLiteralDelimiter !== null) {
throw NonTerminatedStringLiteral::new($tokenOffset - 1);
}
$fragments[] = substr($statement, $fragmentOffset);
$statement = implode('', $fragments);
return [$statement, $paramMap];
}
/**
* Finds next placeholder or opening quote.
*
* @param string $statement The SQL statement to parse
* @param int $tokenOffset The offset to start searching from
* @param int $fragmentOffset The offset to build the next fragment from
* @param string[] $fragments Fragments of the original statement
* not containing placeholders
* @param string|null $currentLiteralDelimiter The delimiter of the current string literal
* or NULL if not currently in a literal
* @param array<int, string> $paramMap Mapping of the original parameter positions
* to their named replacements
*
* @return bool Whether the token was found
*/
private static function findPlaceholderOrOpeningQuote(
$statement,
&$tokenOffset,
&$fragmentOffset,
&$fragments,
&$currentLiteralDelimiter,
&$paramMap
) {
$token = self::findToken($statement, $tokenOffset, '/[?\'"]/');
if (! $token) {
return false;
}
if ($token === '?') {
$position = count($paramMap) + 1;
$param = ':param' . $position;
$fragments[] = substr($statement, $fragmentOffset, $tokenOffset - $fragmentOffset);
$fragments[] = $param;
$paramMap[$position] = $param;
$tokenOffset += 1;
$fragmentOffset = $tokenOffset;
return true;
}
$currentLiteralDelimiter = $token;
++$tokenOffset;
return true;
}
/**
* Finds closing quote
*
* @param string $statement The SQL statement to parse
* @param int $tokenOffset The offset to start searching from
* @param string $currentLiteralDelimiter The delimiter of the current string literal
*
* @return bool Whether the token was found
*
* @param-out string|null $currentLiteralDelimiter
*/
private static function findClosingQuote(
$statement,
&$tokenOffset,
&$currentLiteralDelimiter
) {
$token = self::findToken(
$statement,
$tokenOffset,
'/' . preg_quote($currentLiteralDelimiter, '/') . '/'
);
if (! $token) {
return false;
}
$currentLiteralDelimiter = null;
++$tokenOffset;
return true;
}
/**
* Finds the token described by regex starting from the given offset. Updates the offset with the position
* where the token was found.
*
* @param string $statement The SQL statement to parse
* @param int $offset The offset to start searching from
* @param string $regex The regex containing token pattern
*
* @return string|null Token or NULL if not found
*/
private static function findToken($statement, &$offset, $regex)
{
if (preg_match($regex, $statement, $matches, PREG_OFFSET_CAPTURE, $offset)) {
$offset = $matches[0][1];
return $matches[0][0];
}
return null;
}
/**
* {@inheritdoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
return $this->bindParam($param, $value, $type, null);
}
/**
* {@inheritdoc}
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
{
if (is_int($param)) {
if (! isset($this->_paramMap[$param])) {
throw UnknownParameterIndex::new($param);
}
$param = $this->_paramMap[$param];
}
if ($type === ParameterType::LARGE_OBJECT) {
$lob = oci_new_descriptor($this->_dbh, OCI_D_LOB);
$lob->writeTemporary($variable, OCI_TEMP_BLOB);
$variable =& $lob;
}
$this->boundValues[$param] =& $variable;
return oci_bind_by_name(
$this->_sth,
$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}
*
* @deprecated Use free() instead.
*/
public function closeCursor()
{
$this->free();
return true;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return oci_num_fields($this->_sth) ?: 0;
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorCode()
{
$error = oci_error($this->_sth);
if ($error !== false) {
$error = $error['code'];
}
return $error;
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorInfo()
{
$error = oci_error($this->_sth);
if ($error === false) {
return [];
}
return $error;
}
/**
* {@inheritdoc}
*/
public function execute($params = null)
{
if ($params) {
$hasZeroIndex = array_key_exists(0, $params);
foreach ($params as $key => $val) {
if ($hasZeroIndex && is_int($key)) {
$this->bindValue($key + 1, $val);
} else {
$this->bindValue($key, $val);
}
}
}
$ret = @oci_execute($this->_sth, $this->_conn->getExecuteMode());
if (! $ret) {
throw OCI8Exception::fromErrorInfo($this->errorInfo());
}
$this->result = true;
return $ret;
}
/**
* {@inheritdoc}
*
* @deprecated Use one of the fetch- or iterate-related methods.
*/
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
{
$this->_defaultFetchMode = $fetchMode;
return true;
}
/**
* {@inheritdoc}
*
* @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead.
*/
#[ReturnTypeWillChange]
public function getIterator()
{
return new StatementIterator($this);
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
*/
public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
{
// do not try fetching from the statement if it's not expected to contain result
// in order to prevent exceptional situation
if (! $this->result) {
return false;
}
$fetchMode = $fetchMode ?: $this->_defaultFetchMode;
if ($fetchMode === FetchMode::COLUMN) {
return $this->fetchColumn();
}
if ($fetchMode === FetchMode::STANDARD_OBJECT) {
return oci_fetch_object($this->_sth);
}
if (! isset(self::$fetchModeMap[$fetchMode])) {
throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode);
}
return oci_fetch_array(
$this->_sth,
self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | OCI_RETURN_LOBS
);
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
*/
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
{
$fetchMode = $fetchMode ?: $this->_defaultFetchMode;
$result = [];
if ($fetchMode === FetchMode::STANDARD_OBJECT) {
while ($row = $this->fetch($fetchMode)) {
$result[] = $row;
}
return $result;
}
if (! isset(self::$fetchModeMap[$fetchMode])) {
throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode);
}
if (self::$fetchModeMap[$fetchMode] === OCI_BOTH) {
while ($row = $this->fetch($fetchMode)) {
$result[] = $row;
}
} else {
$fetchStructure = OCI_FETCHSTATEMENT_BY_ROW;
if ($fetchMode === FetchMode::COLUMN) {
$fetchStructure = OCI_FETCHSTATEMENT_BY_COLUMN;
}
// do not try fetching from the statement if it's not expected to contain result
// in order to prevent exceptional situation
if (! $this->result) {
return [];
}
oci_fetch_all(
$this->_sth,
$result,
0,
-1,
self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS
);
if ($fetchMode === FetchMode::COLUMN) {
$result = $result[0];
}
}
return $result;
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchOne() instead.
*/
public function fetchColumn($columnIndex = 0)
{
// do not try fetching from the statement if it's not expected to contain result
// in order to prevent exceptional situation
if (! $this->result) {
return false;
}
$row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS);
if ($row === false) {
return false;
}
return $row[$columnIndex] ?? null;
}
/**
* {@inheritdoc}
*/
public function rowCount()
{
return oci_num_rows($this->_sth) ?: 0;
}
/**
* {@inheritdoc}
*/
public function fetchNumeric()
{
return $this->doFetch(OCI_NUM);
}
/**
* {@inheritdoc}
*/
public function fetchAssociative()
{
return $this->doFetch(OCI_ASSOC);
}
/**
* {@inheritdoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritdoc}
*/
public function fetchAllNumeric(): array
{
return $this->doFetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW);
}
/**
* {@inheritdoc}
*/
public function fetchAllAssociative(): array
{
return $this->doFetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW);
}
/**
* {@inheritdoc}
*/
public function fetchFirstColumn(): array
{
return $this->doFetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0];
}
public function free(): void
{
// not having the result means there's nothing to close
if (! $this->result) {
return;
}
oci_cancel($this->_sth);
$this->result = false;
}
/**
* @return mixed|false
*/
private function doFetch(int $mode)
{
// do not try fetching from the statement if it's not expected to contain the result
// in order to prevent exceptional situation
if (! $this->result) {
return false;
}
return oci_fetch_array(
$this->_sth,
$mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS
);
}
/**
* @return array<mixed>
*/
private function doFetchAll(int $mode, int $fetchStructure): array
{
// do not try fetching from the statement if it's not expected to contain the result
// in order to prevent exceptional situation
if (! $this->result) {
return [];
}
oci_fetch_all(
$this->_sth,
$result,
0,
-1,
$mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS
);
return $result;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Doctrine\DBAL\Driver\OCI8;
final class Statement extends OCI8Statement
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\PDOConnection;
class Connection extends PDOConnection
{
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\PDOException;
/**
* @internal
*
* @psalm-immutable
*/
final class Exception extends PDOException
{
public static function new(\PDOException $exception): self
{
return new self($exception);
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\MySQL;
use Doctrine\DBAL\Driver\PDOMySql;
final class Driver extends PDOMySql\Driver
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\OCI;
use Doctrine\DBAL\Driver\PDOOracle;
final class Driver extends PDOOracle\Driver
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\PgSQL;
use Doctrine\DBAL\Driver\PDOPgSql;
final class Driver extends PDOPgSql\Driver
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\SQLSrv;
use Doctrine\DBAL\Driver\PDOSqlsrv;
final class Connection extends PDOSqlsrv\Connection
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\SQLSrv;
use Doctrine\DBAL\Driver\PDOSqlsrv;
final class Driver extends PDOSqlsrv\Driver
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\SQLSrv;
use Doctrine\DBAL\Driver\PDOSqlsrv;
final class Statement extends PDOSqlsrv\Statement
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\SQLite;
use Doctrine\DBAL\Driver\PDOSqlite;
final class Driver extends PDOSqlite\Driver
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\PDOStatement;
class Statement extends PDOStatement
{
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\DBAL\Driver\PDO\Statement;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use PDO;
use PDOException;
use PDOStatement;
use ReturnTypeWillChange;
use function assert;
/**
* PDO implementation of the Connection interface.
* Used by all PDO-based drivers.
*
* @deprecated Use {@link Connection} instead
*/
class PDOConnection extends PDO implements ConnectionInterface, ServerInfoAwareConnection
{
use PDOQueryImplementation;
/**
* @internal The connection can be only instantiated by its driver.
*
* @param string $dsn
* @param string|null $user
* @param string|null $password
* @param mixed[]|null $options
*
* @throws PDOException In case of an error.
*/
public function __construct($dsn, $user = null, $password = null, ?array $options = null)
{
try {
parent::__construct($dsn, (string) $user, (string) $password, (array) $options);
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, [Statement::class, []]);
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*/
#[ReturnTypeWillChange]
public function exec($sql)
{
try {
$result = parent::exec($sql);
assert($result !== false);
return $result;
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*/
public function getServerVersion()
{
return PDO::getAttribute(PDO::ATTR_SERVER_VERSION);
}
/**
* @param string $sql
* @param array<int, int> $driverOptions
*
* @return PDOStatement
*/
#[ReturnTypeWillChange]
public function prepare($sql, $driverOptions = [])
{
try {
$statement = parent::prepare($sql, $driverOptions);
assert($statement instanceof PDOStatement);
return $statement;
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*/
#[ReturnTypeWillChange]
public function quote($value, $type = ParameterType::STRING)
{
return parent::quote($value, $type);
}
/**
* {@inheritdoc}
*
* @param string|null $name
*
* @return string|int|false
*/
#[ReturnTypeWillChange]
public function lastInsertId($name = null)
{
try {
if ($name === null) {
return parent::lastInsertId();
}
return parent::lastInsertId($name);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*/
public function requiresQueryForServerVersion()
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4114',
'ServerInfoAwareConnection::requiresQueryForServerVersion() is deprecated and removed in DBAL 3.'
);
return false;
}
/**
* @param mixed ...$args
*/
private function doQuery(...$args): PDOStatement
{
try {
$stmt = parent::query(...$args);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
assert($stmt instanceof PDOStatement);
return $stmt;
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\Deprecations\Deprecation;
/**
* @deprecated Use {@link Exception} instead
*
* @psalm-immutable
*/
class PDOException extends \PDOException implements DriverException
{
/**
* The driver specific error code.
*
* @var int|string|null
*/
private $errorCode;
/**
* The SQLSTATE of the driver.
*
* @var string|null
*/
private $sqlState;
/**
* @param \PDOException $exception The PDO exception to wrap.
*/
public function __construct(\PDOException $exception)
{
parent::__construct($exception->getMessage(), 0, $exception);
$this->code = $exception->getCode();
$this->errorInfo = $exception->errorInfo;
$this->errorCode = $exception->errorInfo[1] ?? $exception->getCode();
$this->sqlState = $exception->errorInfo[0] ?? $exception->getCode();
}
/**
* {@inheritdoc}
*/
public function getErrorCode()
{
/** @psalm-suppress ImpureMethodCall */
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4112',
'Driver\AbstractException::getErrorCode() is deprecated, use getSQLState() or getCode() instead.'
);
return $this->errorCode;
}
/**
* {@inheritdoc}
*/
public function getSQLState()
{
return $this->sqlState;
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Doctrine\DBAL\Driver\PDOIbm;
use Doctrine\DBAL\Driver\AbstractDB2Driver;
use Doctrine\DBAL\Driver\PDO\Connection;
use Doctrine\Deprecations\Deprecation;
/**
* Driver for the PDO IBM extension.
*
* @deprecated Use the driver based on the ibm_db2 extension instead.
*/
class Driver extends AbstractDB2Driver
{
/**
* {@inheritdoc}
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
return new Connection(
$this->_constructPdoDsn($params),
$username,
$password,
$driverOptions
);
}
/**
* Constructs the IBM PDO DSN.
*
* @param mixed[] $params
*
* @return string The DSN.
*/
private function _constructPdoDsn(array $params)
{
$dsn = 'ibm:';
if (isset($params['host'])) {
$dsn .= 'HOSTNAME=' . $params['host'] . ';';
}
if (isset($params['port'])) {
$dsn .= 'PORT=' . $params['port'] . ';';
}
$dsn .= 'PROTOCOL=TCPIP;';
if (isset($params['dbname'])) {
$dsn .= 'DATABASE=' . $params['dbname'] . ';';
}
return $dsn;
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/3580',
'Driver::getName() is deprecated'
);
return 'pdo_ibm';
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Doctrine\DBAL\Driver\PDOMySql;
use Doctrine\DBAL\Driver\AbstractMySQLDriver;
use Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Exception;
use Doctrine\Deprecations\Deprecation;
use PDOException;
/**
* PDO MySql driver.
*
* @deprecated Use {@link PDO\MySQL\Driver} instead.
*/
class Driver extends AbstractMySQLDriver
{
/**
* {@inheritdoc}
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
try {
$conn = new PDO\Connection(
$this->constructPdoDsn($params),
$username,
$password,
$driverOptions
);
} catch (PDOException $e) {
throw Exception::driverException($this, $e);
}
return $conn;
}
/**
* Constructs the MySql PDO DSN.
*
* @param mixed[] $params
*
* @return string The DSN.
*/
protected function constructPdoDsn(array $params)
{
$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;
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/3580',
'Driver::getName() is deprecated'
);
return 'pdo_mysql';
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Doctrine\DBAL\Driver\PDOOracle;
use Doctrine\DBAL\Driver\AbstractOracleDriver;
use Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Exception;
use Doctrine\Deprecations\Deprecation;
use PDOException;
/**
* PDO Oracle driver.
*
* @deprecated Use {@link PDO\OCI\Driver} instead.
*/
class Driver extends AbstractOracleDriver
{
/**
* {@inheritdoc}
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
try {
return new PDO\Connection(
$this->constructPdoDsn($params),
$username,
$password,
$driverOptions
);
} catch (PDOException $e) {
throw Exception::driverException($this, $e);
}
}
/**
* Constructs the Oracle PDO DSN.
*
* @param mixed[] $params
*
* @return string The DSN.
*/
private function constructPdoDsn(array $params)
{
$dsn = 'oci:dbname=' . $this->getEasyConnectString($params);
if (isset($params['charset'])) {
$dsn .= ';charset=' . $params['charset'];
}
return $dsn;
}
/**
* {@inheritdoc}
*/
public function getName()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/3580',
'Driver::getName() is deprecated'
);
return 'pdo_oracle';
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Doctrine\DBAL\Driver\PDOPgSql;
use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
use Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Exception;
use Doctrine\Deprecations\Deprecation;
use PDOException;
use function defined;
/**
* Driver that connects through pdo_pgsql.
*
* @deprecated Use {@link PDO\PgSQL\Driver} instead.
*/
class Driver extends AbstractPostgreSQLDriver
{
/**
* {@inheritdoc}
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
try {
$pdo = new PDO\Connection(
$this->_constructPdoDsn($params),
$username,
$password,
$driverOptions
);
if (
defined('PDO::PGSQL_ATTR_DISABLE_PREPARES')
&& (! isset($driverOptions[\PDO::PGSQL_ATTR_DISABLE_PREPARES])
|| $driverOptions[\PDO::PGSQL_ATTR_DISABLE_PREPARES] === true
)
) {
$pdo->setAttribute(\PDO::PGSQL_ATTR_DISABLE_PREPARES, true);
}
/* defining client_encoding via SET NAMES to avoid inconsistent DSN support
* - the 'client_encoding' connection param only works with postgres >= 9.1
* - passing client_encoding via the 'options' param breaks pgbouncer support
*/
if (isset($params['charset'])) {
$pdo->exec('SET NAMES \'' . $params['charset'] . '\'');
}
return $pdo;
} catch (PDOException $e) {
throw Exception::driverException($this, $e);
}
}
/**
* Constructs the Postgres PDO DSN.
*
* @param mixed[] $params
*
* @return string The DSN.
*/
private function _constructPdoDsn(array $params)
{
$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'])) {
$dsn .= 'dbname=' . $params['default_dbname'] . ';';
} else {
// Used for temporary connections to allow operations like dropping the database currently connected to.
// Connecting without an explicit database does not work, therefore "postgres" database is used
// as it is mostly present in every server setup.
$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'] . ';';
}
return $dsn;
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/3580',
'Driver::getName() is deprecated'
);
return 'pdo_pgsql';
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Doctrine\DBAL\Driver;
use PDOStatement;
use ReturnTypeWillChange;
use function func_get_args;
use const PHP_VERSION_ID;
if (PHP_VERSION_ID >= 80000) {
/**
* @internal
*/
trait PDOQueryImplementation
{
/**
* @return PDOStatement
*/
#[ReturnTypeWillChange]
public function query(?string $query = null, ?int $fetchMode = null, mixed ...$fetchModeArgs)
{
return $this->doQuery($query, $fetchMode, ...$fetchModeArgs);
}
}
} else {
/**
* @internal
*/
trait PDOQueryImplementation
{
/**
* @return PDOStatement
*/
public function query()
{
return $this->doQuery(...func_get_args());
}
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace Doctrine\DBAL\Driver\PDOSqlite;
use Doctrine\DBAL\Driver\AbstractSQLiteDriver;
use Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\Deprecations\Deprecation;
use PDOException;
use function array_merge;
/**
* The PDO Sqlite driver.
*
* @deprecated Use {@link PDO\SQLite\Driver} instead.
*/
class Driver extends AbstractSQLiteDriver
{
/** @var mixed[] */
protected $_userDefinedFunctions = [
'sqrt' => ['callback' => [SqlitePlatform::class, 'udfSqrt'], 'numArgs' => 1],
'mod' => ['callback' => [SqlitePlatform::class, 'udfMod'], 'numArgs' => 2],
'locate' => ['callback' => [SqlitePlatform::class, 'udfLocate'], 'numArgs' => -1],
];
/**
* {@inheritdoc}
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
if (isset($driverOptions['userDefinedFunctions'])) {
$this->_userDefinedFunctions = array_merge(
$this->_userDefinedFunctions,
$driverOptions['userDefinedFunctions']
);
unset($driverOptions['userDefinedFunctions']);
}
try {
$pdo = new PDO\Connection(
$this->_constructPdoDsn($params),
$username,
$password,
$driverOptions
);
} catch (PDOException $ex) {
throw Exception::driverException($this, $ex);
}
foreach ($this->_userDefinedFunctions as $fn => $data) {
$pdo->sqliteCreateFunction($fn, $data['callback'], $data['numArgs']);
}
return $pdo;
}
/**
* Constructs the Sqlite PDO DSN.
*
* @param mixed[] $params
*
* @return string The DSN.
*/
protected function _constructPdoDsn(array $params)
{
$dsn = 'sqlite:';
if (isset($params['path'])) {
$dsn .= $params['path'];
} elseif (isset($params['memory'])) {
$dsn .= ':memory:';
}
return $dsn;
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/3580',
'Driver::getName() is deprecated'
);
return 'pdo_sqlite';
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Doctrine\DBAL\Driver\PDOSqlsrv;
use Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\Result;
/**
* Sqlsrv Connection implementation.
*
* @deprecated Use {@link PDO\SQLSrv\Connection} instead.
*/
class Connection extends PDO\Connection
{
/**
* {@inheritdoc}
*
* @internal The connection can be only instantiated by its driver.
*
* @param string $dsn
* @param string|null $user
* @param string|null $password
* @param mixed[]|null $options
*/
public function __construct($dsn, $user = null, $password = null, ?array $options = null)
{
parent::__construct($dsn, $user, $password, $options);
$this->setAttribute(\PDO::ATTR_STATEMENT_CLASS, [PDO\SQLSrv\Statement::class, []]);
}
/**
* {@inheritDoc}
*/
public function lastInsertId($name = null)
{
if ($name === null) {
return parent::lastInsertId($name);
}
$stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?');
$stmt->execute([$name]);
if ($stmt instanceof Result) {
return $stmt->fetchOne();
}
return $stmt->fetchColumn();
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace Doctrine\DBAL\Driver\PDOSqlsrv;
use Doctrine\DBAL\Driver\AbstractSQLServerDriver;
use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost;
use Doctrine\DBAL\Driver\PDO;
use Doctrine\Deprecations\Deprecation;
use function is_int;
use function sprintf;
/**
* The PDO-based Sqlsrv driver.
*
* @deprecated Use {@link PDO\SQLSrv\Driver} instead.
*/
class Driver extends AbstractSQLServerDriver
{
/**
* {@inheritdoc}
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
$pdoOptions = $dsnOptions = [];
foreach ($driverOptions as $option => $value) {
if (is_int($option)) {
$pdoOptions[$option] = $value;
} else {
$dsnOptions[$option] = $value;
}
}
return new PDO\SQLSrv\Connection(
$this->_constructPdoDsn($params, $dsnOptions),
$username,
$password,
$pdoOptions
);
}
/**
* Constructs the Sqlsrv PDO DSN.
*
* @param mixed[] $params
* @param string[] $connectionOptions
*
* @return string The DSN.
*/
private function _constructPdoDsn(array $params, array $connectionOptions)
{
$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;
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/3580',
'Driver::getName() is deprecated'
);
return 'pdo_sqlsrv';
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Doctrine\DBAL\Driver\PDOSqlsrv;
use Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\ParameterType;
/**
* PDO SQL Server Statement
*
* @deprecated Use {@link PDO\SQLSrv\Statement} instead.
*/
class Statement extends PDO\Statement
{
/**
* {@inheritdoc}
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null)
{
switch ($type) {
case ParameterType::LARGE_OBJECT:
case ParameterType::BINARY:
if ($driverOptions === null) {
$driverOptions = \PDO::SQLSRV_ENCODING_BINARY;
}
break;
case ParameterType::ASCII:
$type = ParameterType::STRING;
$length = 0;
$driverOptions = \PDO::SQLSRV_ENCODING_SYSTEM;
break;
}
return parent::bindParam($param, $variable, $type, $length ?? 0, $driverOptions);
}
/**
* {@inheritdoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
return $this->bindParam($param, $value, $type);
}
}

View File

@@ -0,0 +1,316 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use PDO;
use PDOException;
use ReturnTypeWillChange;
use function array_slice;
use function assert;
use function func_get_args;
use function is_array;
/**
* The PDO implementation of the Statement interface.
* Used by all PDO-based drivers.
*
* @deprecated Use {@link Statement} instead
*/
class PDOStatement extends \PDOStatement implements StatementInterface, Result
{
use PDOStatementImplementations;
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,
];
private const FETCH_MODE_MAP = [
FetchMode::ASSOCIATIVE => PDO::FETCH_ASSOC,
FetchMode::NUMERIC => PDO::FETCH_NUM,
FetchMode::MIXED => PDO::FETCH_BOTH,
FetchMode::STANDARD_OBJECT => PDO::FETCH_OBJ,
FetchMode::COLUMN => PDO::FETCH_COLUMN,
FetchMode::CUSTOM_OBJECT => PDO::FETCH_CLASS,
];
/**
* Protected constructor.
*
* @internal The statement can be only instantiated by its driver connection.
*/
protected function __construct()
{
}
/**
* {@inheritdoc}
*/
#[ReturnTypeWillChange]
public function bindValue($param, $value, $type = ParameterType::STRING)
{
$type = $this->convertParamType($type);
try {
return parent::bindValue($param, $value, $type);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* @param mixed $param
* @param mixed $variable
* @param int $type
* @param int|null $length
* @param mixed $driverOptions
*
* @return bool
*/
#[ReturnTypeWillChange]
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null)
{
$type = $this->convertParamType($type);
try {
return parent::bindParam($param, $variable, $type, ...array_slice(func_get_args(), 3));
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*/
#[ReturnTypeWillChange]
public function closeCursor()
{
try {
return parent::closeCursor();
} catch (PDOException $exception) {
// Exceptions not allowed by the interface.
// In case driver implementations do not adhere to the interface, silence exceptions here.
return true;
}
}
/**
* {@inheritdoc}
*/
#[ReturnTypeWillChange]
public function execute($params = null)
{
try {
return parent::execute($params);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
*/
#[ReturnTypeWillChange]
public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
{
$args = func_get_args();
if (isset($args[0])) {
$args[0] = $this->convertFetchMode($args[0]);
}
try {
return parent::fetch(...$args);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchOne() instead.
*/
#[ReturnTypeWillChange]
public function fetchColumn($columnIndex = 0)
{
try {
return parent::fetchColumn($columnIndex);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@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 free(): void
{
parent::closeCursor();
}
/**
* @param mixed ...$args
*/
private function doSetFetchMode(int $fetchMode, ...$args): bool
{
$fetchMode = $this->convertFetchMode($fetchMode);
// This thin wrapper is necessary to shield against the weird signature
// of PDOStatement::setFetchMode(): even if the second and third
// parameters are optional, PHP will not let us remove it from this
// declaration.
$slice = [];
foreach ($args as $arg) {
if ($arg === null) {
break;
}
$slice[] = $arg;
}
try {
return parent::setFetchMode($fetchMode, ...$slice);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* @param mixed ...$args
*
* @return mixed[]
*/
private function doFetchAll(...$args): array
{
if (isset($args[0])) {
$args[0] = $this->convertFetchMode($args[0]);
}
$slice = [];
foreach ($args as $arg) {
if ($arg === null) {
break;
}
$slice[] = $arg;
}
try {
$data = parent::fetchAll(...$slice);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
assert(is_array($data));
return $data;
}
/**
* Converts DBAL parameter type to PDO parameter type
*
* @param int $type Parameter type
*/
private function convertParamType(int $type): int
{
if (! isset(self::PARAM_TYPE_MAP[$type])) {
// TODO: next major: throw an exception
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/3088',
'Using a PDO parameter type (%d given) is deprecated, ' .
'use \Doctrine\DBAL\Types\Types constants instead.',
$type
);
return $type;
}
return self::PARAM_TYPE_MAP[$type];
}
/**
* Converts DBAL fetch mode to PDO fetch mode
*
* @param int $fetchMode Fetch mode
*/
private function convertFetchMode(int $fetchMode): int
{
if (! isset(self::FETCH_MODE_MAP[$fetchMode])) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/3088',
'Using an unsupported PDO fetch mode or a bitmask of fetch modes (%d given)' .
' is deprecated and will cause an error in Doctrine DBAL 3.0',
$fetchMode
);
return $fetchMode;
}
return self::FETCH_MODE_MAP[$fetchMode];
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Doctrine\DBAL\Driver;
use ReturnTypeWillChange;
use function func_get_args;
use const PHP_VERSION_ID;
if (PHP_VERSION_ID >= 80000) {
/**
* @internal
*/
trait PDOStatementImplementations
{
/**
* @deprecated Use one of the fetch- or iterate-related methods.
*
* @param int $mode
* @param mixed ...$args
*
* @return bool
*/
#[ReturnTypeWillChange]
public function setFetchMode($mode, ...$args)
{
return $this->doSetFetchMode($mode, ...$args);
}
/**
* @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
*
* @param int|null $mode
* @param mixed ...$args
*
* @return mixed[]
*/
#[ReturnTypeWillChange]
public function fetchAll($mode = null, ...$args)
{
return $this->doFetchAll($mode, ...$args);
}
}
} else {
/**
* @internal
*/
trait PDOStatementImplementations
{
/**
* @deprecated Use one of the fetch- or iterate-related methods.
*
* @param int $fetchMode
* @param mixed $arg2
* @param mixed $arg3
*/
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null): bool
{
return $this->doSetFetchMode(...func_get_args());
}
/**
* @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
*
* @param int|null $fetchMode
* @param mixed $fetchArgument
* @param mixed $ctorArgs
*
* @return mixed[]
*/
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
{
return $this->doFetchAll(...func_get_args());
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Doctrine\DBAL\Driver;
/**
* An interface for connections which support a "native" ping method.
*
* @deprecated
*/
interface PingableConnection
{
/**
* Pings the database server to determine if the connection is still
* available. Return true/false based on if that was successful or not.
*
* @return bool
*/
public function ping();
}

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver;
/**
* Driver-level result 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 array<int,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 array<int,array<int,mixed>>
*
* @throws Exception
*/
public function fetchAllNumeric(): array;
/**
* Returns an array containing all of the result rows represented as associative arrays.
*
* @return array<int,array<string,mixed>>
*
* @throws Exception
*/
public function fetchAllAssociative(): array;
/**
* Returns an array containing the values of the first column of the result.
*
* @return array<int,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|string The number of rows.
*/
public function rowCount();
/**
* 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.
*/
public function columnCount();
/**
* Discards the non-fetched portion of the result, enabling the originating statement to be executed again.
*/
public function free(): void;
}

View File

@@ -0,0 +1,110 @@
<?php
namespace Doctrine\DBAL\Driver;
use PDO;
use Traversable;
/**
* Interface for the reading part of a prepare statement only.
*/
interface ResultStatement extends Traversable
{
/**
* Closes the cursor, enabling the statement to be executed again.
*
* @deprecated Use Result::free() instead.
*
* @return bool TRUE on success or FALSE on failure.
*/
public function closeCursor();
/**
* Returns the number of columns in the result set
*
* @return int The number of columns in the result set represented
* by the PDOStatement object. If there is no result set,
* this method should return 0.
*/
public function columnCount();
/**
* Sets the fetch mode to use while iterating this statement.
*
* @deprecated Use one of the fetch- or iterate-related methods.
*
* @param int $fetchMode The fetch mode must be one of the {@link FetchMode} constants.
* @param mixed $arg2
* @param mixed $arg3
*
* @return bool
*/
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null);
/**
* Returns the next row of a result set.
*
* @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
*
* @param int|null $fetchMode Controls how the next row will be returned to the caller.
* The value must be one of the {@link FetchMode} constants,
* defaulting to {@link FetchMode::MIXED}.
* @param int $cursorOrientation For a ResultStatement object representing a scrollable cursor,
* this value determines which row will be returned to the caller.
* This value must be one of the \PDO::FETCH_ORI_* constants,
* defaulting to \PDO::FETCH_ORI_NEXT. To request a scrollable
* cursor for your ResultStatement object, you must set the \PDO::ATTR_CURSOR
* attribute to \PDO::CURSOR_SCROLL when you prepare the SQL statement with
* \PDO::prepare().
* @param int $cursorOffset For a ResultStatement object representing a scrollable cursor for which the
* cursorOrientation parameter is set to \PDO::FETCH_ORI_ABS, this value
* specifies the absolute number of the row in the result set that shall be
* fetched.
* For a ResultStatement object representing a scrollable cursor for which the
* cursorOrientation parameter is set to \PDO::FETCH_ORI_REL, this value
* specifies the row to fetch relative to the cursor position before
* ResultStatement::fetch() was called.
*
* @return mixed The return value of this method on success depends on the fetch mode. In all cases, FALSE is
* returned on failure.
*/
public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0);
/**
* Returns an array containing all of the result set rows.
*
* @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
*
* @param int|null $fetchMode Controls how the next row will be returned to the caller.
* The value must be one of the {@link FetchMode} constants,
* defaulting to {@link FetchMode::MIXED}.
* @param int|string|null $fetchArgument This argument has a different meaning depending on the value
* of the $fetchMode parameter:
* * {@link FetchMode::COLUMN}:
* Returns the indicated 0-indexed column.
* * {@link FetchMode::CUSTOM_OBJECT}:
* Returns instances of the specified class, mapping the columns of each row
* to named properties in the class.
* * {@link PDO::FETCH_FUNC}: Returns the results of calling
* the specified function, using each row's
* columns as parameters in the call.
* @param mixed[]|null $ctorArgs Controls how the next row will be returned to the caller.
* The value must be one of the {@link FetchMode} constants,
* defaulting to {@link FetchMode::MIXED}.
*
* @return mixed[]
*/
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null);
/**
* Returns a single column from the next row of a result set or FALSE if there are no more rows.
*
* @deprecated Use fetchOne() instead.
*
* @param int $columnIndex 0-indexed number of the column you wish to retrieve from the row.
* If no value is supplied, fetches the first column.
*
* @return mixed|false A single column in the next row of a result set, or FALSE if there are no more rows.
*/
public function fetchColumn($columnIndex = 0);
}

View File

@@ -0,0 +1,104 @@
<?php
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\AbstractSQLAnywhereDriver;
use Doctrine\DBAL\Exception;
use Doctrine\Deprecations\Deprecation;
use function array_keys;
use function array_map;
use function implode;
/**
* A Doctrine DBAL driver for the SAP Sybase SQL Anywhere PHP extension.
*
* @deprecated Support for SQLAnywhere will be removed in 3.0.
*/
class Driver extends AbstractSQLAnywhereDriver
{
/**
* {@inheritdoc}
*
* @throws Exception If there was a problem establishing the connection.
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
try {
return new SQLAnywhereConnection(
$this->buildDsn(
$params['host'] ?? null,
$params['port'] ?? null,
$params['server'] ?? null,
$params['dbname'] ?? null,
$username,
$password,
$driverOptions
),
$params['persistent'] ?? false
);
} catch (SQLAnywhereException $e) {
throw Exception::driverException($this, $e);
}
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/3580',
'Driver::getName() is deprecated'
);
return 'sqlanywhere';
}
/**
* Build the connection string for given connection parameters and driver options.
*
* @param string|null $host Host address to connect to.
* @param int|null $port Port to use for the connection (default to SQL Anywhere standard port 2638).
* @param string|null $server Database server name on the host to connect to.
* SQL Anywhere allows multiple database server instances on the same host,
* therefore specifying the server instance name to use is mandatory.
* @param string|null $dbname Name of the database on the server instance to connect to.
* @param string $username User name to use for connection authentication.
* @param string $password Password to use for connection authentication.
* @param mixed[] $driverOptions Additional parameters to use for the connection.
*
* @return string
*/
private function buildDsn(
$host,
$port,
$server,
$dbname,
$username = null,
$password = null,
array $driverOptions = []
) {
$host = $host ?: 'localhost';
$port = $port ?: 2638;
if (! empty($server)) {
$server = ';ServerName=' . $server;
}
return 'HOST=' . $host . ':' . $port .
$server .
';DBN=' . $dbname .
';UID=' . $username .
';PWD=' . $password .
';' . implode(
';',
array_map(static function ($key, $value) {
return $key . '=' . $value;
}, array_keys($driverOptions), $driverOptions)
);
}
}

View File

@@ -0,0 +1,250 @@
<?php
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use function assert;
use function func_get_args;
use function is_float;
use function is_int;
use function is_resource;
use function is_string;
use function sasql_affected_rows;
use function sasql_commit;
use function sasql_connect;
use function sasql_error;
use function sasql_errorcode;
use function sasql_escape_string;
use function sasql_insert_id;
use function sasql_pconnect;
use function sasql_real_query;
use function sasql_rollback;
use function sasql_set_option;
/**
* SAP Sybase SQL Anywhere implementation of the Connection interface.
*
* @deprecated Support for SQLAnywhere will be removed in 3.0.
*/
class SQLAnywhereConnection implements Connection, ServerInfoAwareConnection
{
/** @var resource The SQL Anywhere connection resource. */
private $connection;
/**
* Connects to database with given connection string.
*
* @internal The connection can be only instantiated by its driver.
*
* @param string $dsn The connection string.
* @param bool $persistent Whether or not to establish a persistent connection.
*
* @throws SQLAnywhereException
*/
public function __construct($dsn, $persistent = false)
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4077',
'The SQLAnywhere driver is deprecated'
);
$this->connection = $persistent ? @sasql_pconnect($dsn) : @sasql_connect($dsn);
if (! is_resource($this->connection)) {
throw SQLAnywhereException::fromSQLAnywhereError();
}
// Disable PHP warnings on error.
if (! sasql_set_option($this->connection, 'verbose_errors', false)) {
throw SQLAnywhereException::fromSQLAnywhereError($this->connection);
}
// Enable auto committing by default.
if (! sasql_set_option($this->connection, 'auto_commit', 'on')) {
throw SQLAnywhereException::fromSQLAnywhereError($this->connection);
}
}
/**
* {@inheritdoc}
*
* @throws SQLAnywhereException
*/
public function beginTransaction()
{
if (! sasql_set_option($this->connection, 'auto_commit', 'off')) {
throw SQLAnywhereException::fromSQLAnywhereError($this->connection);
}
return true;
}
/**
* {@inheritdoc}
*
* @throws SQLAnywhereException
*/
public function commit()
{
if (! sasql_commit($this->connection)) {
throw SQLAnywhereException::fromSQLAnywhereError($this->connection);
}
$this->endTransaction();
return true;
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorCode()
{
return sasql_errorcode($this->connection);
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorInfo()
{
return sasql_error($this->connection);
}
/**
* {@inheritdoc}
*/
public function exec($sql)
{
if (sasql_real_query($this->connection, $sql) === false) {
throw SQLAnywhereException::fromSQLAnywhereError($this->connection);
}
return sasql_affected_rows($this->connection);
}
/**
* {@inheritdoc}
*/
public function getServerVersion()
{
$stmt = $this->query("SELECT PROPERTY('ProductVersion')");
if ($stmt instanceof Result) {
$version = $stmt->fetchOne();
} else {
$version = $stmt->fetchColumn();
}
assert(is_string($version));
return $version;
}
/**
* {@inheritdoc}
*/
public function lastInsertId($name = null)
{
if ($name === null) {
return sasql_insert_id($this->connection);
}
$stmt = $this->query('SELECT ' . $name . '.CURRVAL');
if ($stmt instanceof Result) {
return $stmt->fetchOne();
}
return $stmt->fetchColumn();
}
/**
* {@inheritdoc}
*/
public function prepare($sql)
{
return new SQLAnywhereStatement($this->connection, $sql);
}
/**
* {@inheritdoc}
*/
public function query()
{
$args = func_get_args();
$stmt = $this->prepare($args[0]);
$stmt->execute();
return $stmt;
}
/**
* {@inheritdoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
if (is_int($value) || is_float($value)) {
return $value;
}
return "'" . sasql_escape_string($this->connection, $value) . "'";
}
/**
* {@inheritdoc}
*/
public function requiresQueryForServerVersion()
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4114',
'ServerInfoAwareConnection::requiresQueryForServerVersion() is deprecated and removed in DBAL 3.'
);
return true;
}
/**
* {@inheritdoc}
*
* @throws SQLAnywhereException
*/
public function rollBack()
{
if (! sasql_rollback($this->connection)) {
throw SQLAnywhereException::fromSQLAnywhereError($this->connection);
}
$this->endTransaction();
return true;
}
/**
* Ends transactional mode and enables auto commit again.
*
* @return bool Whether or not ending transactional mode succeeded.
*
* @throws SQLAnywhereException
*/
private function endTransaction()
{
if (! sasql_set_option($this->connection, 'auto_commit', 'on')) {
throw SQLAnywhereException::fromSQLAnywhereError($this->connection);
}
return true;
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\AbstractDriverException;
use InvalidArgumentException;
use function sasql_error;
use function sasql_errorcode;
use function sasql_sqlstate;
use function sasql_stmt_errno;
use function sasql_stmt_error;
/**
* SAP Sybase SQL Anywhere driver exception.
*
* @deprecated Support for SQLAnywhere will be removed in 3.0.
*
* @psalm-immutable
*/
class SQLAnywhereException extends AbstractDriverException
{
/**
* Helper method to turn SQL Anywhere error into exception.
*
* @param resource|null $conn The SQL Anywhere connection resource to retrieve the last error from.
* @param resource|null $stmt The SQL Anywhere statement resource to retrieve the last error from.
*
* @return SQLAnywhereException
*
* @throws InvalidArgumentException
*/
public static function fromSQLAnywhereError($conn = null, $stmt = null)
{
$state = $conn ? sasql_sqlstate($conn) : sasql_sqlstate();
$code = null;
$message = null;
/**
* Try retrieving the last error from statement resource if given
*/
if ($stmt) {
$code = sasql_stmt_errno($stmt);
$message = sasql_stmt_error($stmt);
}
/**
* Try retrieving the last error from the connection resource
* if either the statement resource is not given or the statement
* resource is given but the last error could not be retrieved from it (fallback).
* Depending on the type of error, it is sometimes necessary to retrieve
* it from the connection resource even though it occurred during
* a prepared statement.
*/
if ($conn && ! $code) {
$code = sasql_errorcode($conn);
$message = sasql_error($conn);
}
/**
* Fallback mode if either no connection resource is given
* or the last error could not be retrieved from the given
* connection / statement resource.
*/
if (! $conn || ! $code) {
$code = sasql_errorcode();
$message = sasql_error();
}
if ($message) {
return new self('SQLSTATE [' . $state . '] [' . $code . '] ' . $message, $state, $code);
}
return new self('SQL Anywhere error occurred but no error message was retrieved from driver.', $state, $code);
}
}

View File

@@ -0,0 +1,469 @@
<?php
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Driver\StatementIterator;
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
use IteratorAggregate;
use PDO;
use ReflectionClass;
use ReflectionObject;
use ReturnTypeWillChange;
use stdClass;
use function array_key_exists;
use function assert;
use function func_get_args;
use function func_num_args;
use function gettype;
use function is_array;
use function is_int;
use function is_object;
use function is_resource;
use function is_string;
use function sasql_fetch_array;
use function sasql_fetch_assoc;
use function sasql_fetch_object;
use function sasql_fetch_row;
use function sasql_prepare;
use function sasql_stmt_affected_rows;
use function sasql_stmt_bind_param_ex;
use function sasql_stmt_errno;
use function sasql_stmt_error;
use function sasql_stmt_execute;
use function sasql_stmt_field_count;
use function sasql_stmt_reset;
use function sasql_stmt_result_metadata;
use function sprintf;
use const SASQL_BOTH;
/**
* SAP SQL Anywhere implementation of the Statement interface.
*
* @deprecated Support for SQLAnywhere will be removed in 3.0.
*/
class SQLAnywhereStatement implements IteratorAggregate, Statement, Result
{
/** @var resource The connection resource. */
private $conn;
/** @var string Name of the default class to instantiate when fetching class instances. */
private $defaultFetchClass = '\stdClass';
/** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */
private $defaultFetchClassCtorArgs = [];
/** @var int Default fetch mode to use. */
private $defaultFetchMode = FetchMode::MIXED;
/** @var resource|null The result set resource to fetch. */
private $result;
/** @var resource The prepared SQL statement to execute. */
private $stmt;
/** @var mixed[] The references to bound parameter values. */
private $boundValues = [];
/**
* Prepares given statement for given connection.
*
* @internal The statement can be only instantiated by its driver connection.
*
* @param resource $conn The connection resource to use.
* @param string $sql The SQL statement to prepare.
*
* @throws SQLAnywhereException
*/
public function __construct($conn, $sql)
{
if (! is_resource($conn)) {
throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn);
}
$this->conn = $conn;
$this->stmt = sasql_prepare($conn, $sql);
if (! is_resource($this->stmt)) {
throw SQLAnywhereException::fromSQLAnywhereError($conn);
}
}
/**
* {@inheritdoc}
*
* @throws SQLAnywhereException
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
{
assert(is_int($param));
switch ($type) {
case ParameterType::INTEGER:
case ParameterType::BOOLEAN:
$type = 'i';
break;
case ParameterType::LARGE_OBJECT:
$type = 'b';
break;
case ParameterType::NULL:
case ParameterType::STRING:
case ParameterType::BINARY:
$type = 's';
break;
default:
throw new SQLAnywhereException('Unknown type: ' . $type);
}
$this->boundValues[$param] =& $variable;
if (! sasql_stmt_bind_param_ex($this->stmt, $param - 1, $variable, $type, $variable === null)) {
throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
}
return true;
}
/**
* {@inheritdoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
assert(is_int($param));
return $this->bindParam($param, $value, $type);
}
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*
* @throws SQLAnywhereException
*/
public function closeCursor()
{
if (! sasql_stmt_reset($this->stmt)) {
throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
}
return true;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return sasql_stmt_field_count($this->stmt);
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorCode()
{
return sasql_stmt_errno($this->stmt);
}
/**
* {@inheritdoc}
*
* @deprecated The error information is available via exceptions.
*/
public function errorInfo()
{
return sasql_stmt_error($this->stmt);
}
/**
* {@inheritdoc}
*
* @throws SQLAnywhereException
*/
public function execute($params = null)
{
if (is_array($params)) {
$hasZeroIndex = array_key_exists(0, $params);
foreach ($params as $key => $val) {
if ($hasZeroIndex && is_int($key)) {
$this->bindValue($key + 1, $val);
} else {
$this->bindValue($key, $val);
}
}
}
if (! sasql_stmt_execute($this->stmt)) {
throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
}
$this->result = sasql_stmt_result_metadata($this->stmt);
return true;
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead.
*
* @throws SQLAnywhereException
*/
public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
{
if (! is_resource($this->result)) {
return false;
}
$fetchMode = $fetchMode ?: $this->defaultFetchMode;
switch ($fetchMode) {
case FetchMode::COLUMN:
return $this->fetchColumn();
case FetchMode::ASSOCIATIVE:
return sasql_fetch_assoc($this->result);
case FetchMode::MIXED:
return sasql_fetch_array($this->result, SASQL_BOTH);
case FetchMode::CUSTOM_OBJECT:
$className = $this->defaultFetchClass;
$ctorArgs = $this->defaultFetchClassCtorArgs;
if (func_num_args() >= 2) {
$args = func_get_args();
$className = $args[1];
$ctorArgs = $args[2] ?? [];
}
$result = sasql_fetch_object($this->result);
if ($result instanceof stdClass) {
$result = $this->castObject($result, $className, $ctorArgs);
}
return $result;
case FetchMode::NUMERIC:
return sasql_fetch_row($this->result);
case FetchMode::STANDARD_OBJECT:
return sasql_fetch_object($this->result);
default:
throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode);
}
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.
*/
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
{
$rows = [];
switch ($fetchMode) {
case FetchMode::CUSTOM_OBJECT:
while (($row = $this->fetch(...func_get_args())) !== false) {
$rows[] = $row;
}
break;
case FetchMode::COLUMN:
while (($row = $this->fetchColumn()) !== false) {
$rows[] = $row;
}
break;
default:
while (($row = $this->fetch($fetchMode)) !== false) {
$rows[] = $row;
}
}
return $rows;
}
/**
* {@inheritdoc}
*
* @deprecated Use fetchOne() instead.
*/
public function fetchColumn($columnIndex = 0)
{
$row = $this->fetch(FetchMode::NUMERIC);
if ($row === false) {
return false;
}
return $row[$columnIndex] ?? null;
}
/**
* {@inheritdoc}
*
* @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead.
*/
#[ReturnTypeWillChange]
public function getIterator()
{
return new StatementIterator($this);
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
if (! is_resource($this->result)) {
return false;
}
return sasql_fetch_row($this->result);
}
/**
* {@inheritdoc}
*/
public function fetchAssociative()
{
if (! is_resource($this->result)) {
return false;
}
return sasql_fetch_assoc($this->result);
}
/**
* {@inheritdoc}
*
* @throws Exception
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* @return array<int,array<int,mixed>>
*
* @throws Exception
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* @return array<int,array<string,mixed>>
*
* @throws Exception
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* @return array<int,mixed>
*
* @throws Exception
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
/**
* {@inheritdoc}
*/
public function rowCount()
{
return sasql_stmt_affected_rows($this->stmt);
}
public function free(): void
{
sasql_stmt_reset($this->stmt);
}
/**
* {@inheritdoc}
*
* @deprecated Use one of the fetch- or iterate-related methods.
*/
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
{
$this->defaultFetchMode = $fetchMode;
$this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass;
$this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
return true;
}
/**
* Casts a stdClass object to the given class name mapping its' properties.
*
* @param stdClass $sourceObject Object to cast from.
* @param class-string|object $destinationClass Name of the class or class instance to cast to.
* @param mixed[] $ctorArgs Arguments to use for constructing the destination class instance.
*
* @return object
*
* @throws SQLAnywhereException
*/
private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
{
if (! is_string($destinationClass)) {
if (! is_object($destinationClass)) {
throw new SQLAnywhereException(sprintf(
'Destination class has to be of type string or object, %s given.',
gettype($destinationClass)
));
}
} else {
$destinationClass = new ReflectionClass($destinationClass);
$destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
}
$sourceReflection = new ReflectionObject($sourceObject);
$destinationClassReflection = new ReflectionObject($destinationClass);
foreach ($sourceReflection->getProperties() as $sourceProperty) {
$sourceProperty->setAccessible(true);
$name = $sourceProperty->getName();
$value = $sourceProperty->getValue($sourceObject);
if ($destinationClassReflection->hasProperty($name)) {
$destinationProperty = $destinationClassReflection->getProperty($name);
$destinationProperty->setAccessible(true);
$destinationProperty->setValue($destinationClass, $value);
} else {
$destinationClass->$name = $value;
}
}
return $destinationClass;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Doctrine\DBAL\Driver\SQLSrv;
final class Connection extends SQLSrvConnection
{
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Doctrine\DBAL\Driver\SQLSrv;
use Doctrine\DBAL\Driver\AbstractSQLServerDriver;
use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost;
use Doctrine\Deprecations\Deprecation;
/**
* Driver for ext/sqlsrv.
*/
class Driver extends AbstractSQLServerDriver
{
/**
* {@inheritdoc}
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
$serverName = '';
if (isset($params['host'])) {
$serverName = $params['host'];
if (isset($params['port'])) {
$serverName .= ',' . $params['port'];
}
} elseif (isset($params['port'])) {
throw PortWithoutHost::new();
}
if (isset($params['dbname'])) {
$driverOptions['Database'] = $params['dbname'];
}
if (isset($params['charset'])) {
$driverOptions['CharacterSet'] = $params['charset'];
}
if ($username !== null) {
$driverOptions['UID'] = $username;
}
if ($password !== null) {
$driverOptions['PWD'] = $password;
}
if (! isset($driverOptions['ReturnDatesAsStrings'])) {
$driverOptions['ReturnDatesAsStrings'] = 1;
}
return new Connection($serverName, $driverOptions);
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/3580',
'Driver::getName() is deprecated'
);
return 'sqlsrv';
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\SQLSrv\Exception;
use Doctrine\DBAL\Driver\SQLSrv\SQLSrvException;
use function rtrim;
use function sqlsrv_errors;
use const SQLSRV_ERR_ERRORS;
/**
* @internal
*
* @psalm-immutable
*/
final class Error extends SQLSrvException
{
public static function new(): self
{
$message = '';
$sqlState = null;
$errorCode = null;
foreach ((array) sqlsrv_errors(SQLSRV_ERR_ERRORS) as $error) {
$message .= 'SQLSTATE [' . $error['SQLSTATE'] . ', ' . $error['code'] . ']: ' . $error['message'] . "\n";
if ($sqlState === null) {
$sqlState = $error['SQLSTATE'];
}
if ($errorCode !== null) {
continue;
}
$errorCode = $error['code'];
}
if (! $message) {
$message = 'SQL Server error occurred but no error message was retrieved from driver.';
}
return new self(rtrim($message), $sqlState, $errorCode);
}
}

Some files were not shown because too many files have changed in this diff Show More