Unverified Commit 97fe49a7 authored by Sergei Morozov's avatar Sergei Morozov Committed by GitHub

Merge pull request #4125 from morozov/remove-deprecated-driver-classes

Remove deprecated driver classes
parents a77c0074 8f9e9021
# Upgrade to 3.0
## BC BREAK: Deprecated driver-level classes and interfaces are removed.
- `AbstractDriverException`
- `DriverException`
- `PDOConnection`
- `PDOException`
- `PDOStatement`
- `IBMDB2\DB2Connection`
- `IBMDB2\DB2Driver`
- `IBMDB2\DB2Exception`
- `IBMDB2\DB2Statement`
- `Mysqli\MysqliConnection`
- `Mysqli\MysqliException`
- `Mysqli\MysqliStatement`
- `OCI8\OCI8Connection`
- `OCI8\OCI8Exception`
- `OCI8\OCI8Statement`
- `SQLSrv\SQLSrvConnection`
- `SQLSrv\SQLSrvException`
- `SQLSrv\SQLSrvStatement`
## BC BREAK: `ServerInfoAwareConnection::requiresQueryForServerVersion()` is removed.
The `ServerInfoAwareConnection::requiresQueryForServerVersion()` method has been removed as an implementation detail which is the same for all supported drivers.
......
......@@ -87,9 +87,8 @@
phpcs wrongly complains about them, and that has been reported here:
https://github.com/squizlabs/PHP_CodeSniffer/issues/2950
-->
<exclude-pattern>src/Driver/IBMDB2/DB2Connection.php</exclude-pattern>
<exclude-pattern>src/Driver/IBMDB2/Connection.php</exclude-pattern>
<exclude-pattern>src/Driver/Mysqli/Exception/ConnectionFailed.php</exclude-pattern>
<exclude-pattern>src/Driver/Mysqli/MysqliConnection.php</exclude-pattern>
<!-- See https://github.com/squizlabs/PHP_CodeSniffer/issues/2837 -->
<exclude-pattern>src/SQLParserUtils.php</exclude-pattern>
<exclude-pattern>src/Tools/Dumper.php</exclude-pattern>
......@@ -112,15 +111,9 @@
<exclude-pattern>src/Schema/Comparator.php</exclude-pattern>
</rule>
<!-- DB2_AUTOCOMMIT_ON/DB2_AUTOCOMMIT_OFF are of int type but db2_autocommit() incorrectly expects bool,
see https://bugs.php.net/bug.php?id=77591 -->
<rule ref="SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing">
<exclude-pattern>src/Driver/IBMDB2/DB2Connection.php</exclude-pattern>
</rule>
<!-- The SQLSRV_* functions are defined in the upper case by the sqlsrv extension and violate the standard
see https://docs.microsoft.com/en-us/sql/connect/php/constants-microsoft-drivers-for-php-for-sql-server -->
<rule ref="Squiz.PHP.LowercasePHPFunctions">
<exclude-pattern>src/Driver/SQLSrv/SQLSrvStatement.php</exclude-pattern>
<exclude-pattern>src/Driver/SQLSrv/Statement.php</exclude-pattern>
</rule>
</ruleset>
......@@ -19,7 +19,7 @@ parameters:
# changing these would be a BC break, to be done in next major
- "~^Casting to bool something that's already bool.~"
- '~^Property Doctrine\\DBAL\\Schema\\Schema::\$_schemaConfig \(Doctrine\\DBAL\\Schema\\SchemaConfig\) does not accept default value of type false\.\z~'
- '~^Return type \(int\|false\) of method Doctrine\\DBAL\\Driver\\OCI8\\OCI8Connection\:\:lastInsertId\(\) should be compatible with return type \(string\) of method Doctrine\\DBAL\\Driver\\Connection::lastInsertId\(\)~'
- '~^Return type \(int\|false\) of method Doctrine\\DBAL\\Driver\\OCI8\\Connection\:\:lastInsertId\(\) should be compatible with return type \(string\) of method Doctrine\\DBAL\\Driver\\Connection::lastInsertId\(\)~'
# https://github.com/phpstan/phpstan/issues/2857
# TODO: remove in 4.0.0
......@@ -48,12 +48,12 @@ parameters:
# Requires a release of https://github.com/JetBrains/phpstorm-stubs/pull/553
-
message: '~^Call to function assert\(\) with true will always evaluate to true\.$~'
path: %currentWorkingDirectory%/src/Driver/PDOConnection.php
path: %currentWorkingDirectory%/src/Driver/PDO/Connection.php
# Requires a release of https://github.com/JetBrains/phpstorm-stubs/pull/553
-
message: '~^Strict comparison using !== between int and false will always evaluate to true\.$~'
path: %currentWorkingDirectory%/src/Driver/PDOConnection.php
path: %currentWorkingDirectory%/src/Driver/PDO/Connection.php
# False Positive
- '~Strict comparison using === between 1 and 2 will always evaluate to false~'
......
......@@ -38,7 +38,7 @@
Fixing these issues requires an API change
-->
<file name="src/Driver/PDOSqlsrv/Connection.php"/>
<file name="src/Driver/SQLSrv/SQLSrvConnection.php"/>
<file name="src/Driver/SQLSrv/Connection.php"/>
</errorLevel>
</FalsableReturnStatement>
<NullableReturnStatement>
......@@ -55,7 +55,7 @@
Requires a release of
https://github.com/JetBrains/phpstorm-stubs/pull/727
-->
<file name="src/Driver/SQLSrv/SQLSrvStatement.php"/>
<file name="src/Driver/SQLSrv/Statement.php"/>
</errorLevel>
</TooFewArguments>
<UndefinedConstant>
......
......@@ -10,7 +10,7 @@ use Doctrine\DBAL\Cache\CacheException;
use Doctrine\DBAL\Cache\CachingResult;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Driver\Exception as DriverException;
use Doctrine\DBAL\Driver\PingableConnection;
use Doctrine\DBAL\Driver\Result as DriverResult;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
......
......@@ -8,7 +8,7 @@ use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Driver\Exception as DriverException;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Event\ConnectionEventArgs;
......
......@@ -2,7 +2,7 @@
namespace Doctrine\DBAL;
use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException;
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Driver\ExceptionConverterDriver;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
......@@ -169,7 +169,7 @@ class DBALException extends Exception
return $driverEx;
}
if ($driver instanceof ExceptionConverterDriver && $driverEx instanceof DeprecatedDriverException) {
if ($driver instanceof ExceptionConverterDriver && $driverEx instanceof TheDriverException) {
return $driver->convertException($msg, $driverEx);
}
......
......@@ -3,7 +3,7 @@
namespace Doctrine\DBAL;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
......@@ -20,7 +20,7 @@ interface Driver
*
* @return DriverConnection The database connection.
*
* @throws DriverException
* @throws Exception
*/
public function connect(array $params);
......
......@@ -4,7 +4,7 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as TheDriverException;
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Schema\DB2SchemaManager;
......
<?php
namespace Doctrine\DBAL\Driver;
/**
* @deprecated
*
* @psalm-immutable
*/
abstract class AbstractDriverException extends AbstractException
{
}
......@@ -14,7 +14,7 @@ use Throwable;
*
* @psalm-immutable
*/
abstract class AbstractException extends BaseException implements DriverException
abstract class AbstractException extends BaseException implements Exception
{
/**
* The SQLSTATE of the driver.
......
......@@ -4,7 +4,7 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException;
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\ConnectionLost;
use Doctrine\DBAL\Exception\DeadlockException;
......@@ -40,7 +40,7 @@ abstract class AbstractMySQLDriver implements ExceptionConverterDriver, VersionA
* @link https://dev.mysql.com/doc/refman/8.0/en/client-error-reference.html
* @link https://dev.mysql.com/doc/refman/8.0/en/server-error-reference.html
*/
public function convertException($message, DeprecatedDriverException $exception)
public function convertException($message, TheDriverException $exception)
{
switch ($exception->getCode()) {
case 1213:
......
......@@ -5,7 +5,7 @@ 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\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
......@@ -27,7 +27,7 @@ abstract class AbstractOracleDriver implements Driver, ExceptionConverterDriver
/**
* {@inheritdoc}
*/
public function convertException($message, DeprecatedDriverException $exception)
public function convertException($message, TheDriverException $exception)
{
switch ($exception->getCode()) {
case 1:
......
......@@ -4,7 +4,7 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException;
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\DriverException;
......@@ -35,7 +35,7 @@ abstract class AbstractPostgreSQLDriver implements ExceptionConverterDriver, Ver
*
* @link http://www.postgresql.org/docs/9.4/static/errcodes-appendix.html
*/
public function convertException($message, DeprecatedDriverException $exception)
public function convertException($message, TheDriverException $exception)
{
switch ($exception->getSQLState()) {
case '40001':
......
......@@ -4,7 +4,7 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as TheDriverException;
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
use Doctrine\DBAL\Schema\SQLServerSchemaManager;
......
......@@ -4,7 +4,7 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException;
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
......@@ -32,7 +32,7 @@ abstract class AbstractSQLiteDriver implements Driver, ExceptionConverterDriver
*
* @link http://www.sqlite.org/c3ref/c_abort.html
*/
public function convertException($message, DeprecatedDriverException $exception)
public function convertException($message, TheDriverException $exception)
{
if (strpos($exception->getMessage(), 'database is locked') !== false) {
return new LockWaitTimeoutException($message, $exception);
......
<?php
namespace Doctrine\DBAL\Driver;
/**
* @deprecated Use {@link Exception} instead
*
* @psalm-immutable
*/
interface DriverException extends Exception
{
}
......@@ -3,7 +3,7 @@
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as TheDriverException;
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Exception\DriverException;
/**
......
......@@ -2,6 +2,162 @@
namespace Doctrine\DBAL\Driver\IBMDB2;
final class Connection extends DB2Connection
use Doctrine\DBAL\Driver\Exception;
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\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use stdClass;
use function assert;
use function db2_autocommit;
use function db2_commit;
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 is_bool;
use const DB2_AUTOCOMMIT_OFF;
use const DB2_AUTOCOMMIT_ON;
final class Connection implements ServerInfoAwareConnection
{
/** @var resource */
private $conn = null;
/**
* @param array<string,mixed> $driverOptions
*
* @throws Exception
*/
public function __construct(
string $database,
bool $persistent,
string $username,
string $password,
array $driverOptions = []
) {
if ($persistent) {
$conn = db2_pconnect($database, $username, $password, $driverOptions);
} else {
$conn = db2_connect($database, $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;
}
public function prepare(string $sql): DriverStatement
{
$stmt = @db2_prepare($this->conn, $sql);
if ($stmt === false) {
throw PrepareFailed::new(error_get_last()['message']);
}
return new Statement($stmt);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritdoc}
*/
public function quote($input, $type = ParameterType::STRING)
{
$input = db2_escape_string($input);
if ($type === ParameterType::INTEGER) {
return $input;
}
return "'" . $input . "'";
}
public function exec(string $statement): int
{
$stmt = @db2_exec($this->conn, $statement);
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;
}
}
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
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\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use stdClass;
use function assert;
use function db2_autocommit;
use function db2_commit;
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 is_bool;
use const DB2_AUTOCOMMIT_OFF;
use const DB2_AUTOCOMMIT_ON;
/**
* @deprecated Use {@link Connection} instead
*/
class DB2Connection implements ServerInfoAwareConnection
{
/** @var resource */
private $conn = null;
/**
* @param array<string,mixed> $driverOptions
*
* @throws DB2Exception
*/
public function __construct(
string $database,
bool $persistent,
string $username,
string $password,
array $driverOptions = []
) {
if ($persistent) {
$conn = db2_pconnect($database, $username, $password, $driverOptions);
} else {
$conn = db2_connect($database, $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;
}
public function prepare(string $sql): DriverStatement
{
$stmt = @db2_prepare($this->conn, $sql);
if ($stmt === false) {
throw PrepareFailed::new(error_get_last()['message']);
}
return new Statement($stmt);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritdoc}
*/
public function quote($input, $type = ParameterType::STRING)
{
$input = db2_escape_string($input);
if ($type === ParameterType::INTEGER) {
return $input;
}
return "'" . $input . "'";
}
public function exec(string $statement): int
{
$stmt = @db2_exec($this->conn, $statement);
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;
}
}
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\AbstractDB2Driver;
/**
* IBM DB2 Driver.
*
* @deprecated Use {@link Driver} instead
*/
class DB2Driver extends AbstractDB2Driver
{
/**
* {@inheritdoc}
*/
public function connect(array $params)
{
return new Connection(
DataSourceName::fromConnectionParameters($params)->toString(),
isset($params['persistent']) && $params['persistent'] === true,
$params['user'] ?? '',
$params['password'] ?? '',
$params['driver_options'] ?? []
);
}
}
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\AbstractDriverException;
/**
* @deprecated Use {@link Exception} instead
*
* @psalm-immutable
*/
class DB2Exception extends AbstractDriverException
{
}
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
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 as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use function assert;
use function db2_bind_param;
use function db2_execute;
use function error_get_last;
use function fclose;
use function fwrite;
use function is_int;
use function is_resource;
use function ksort;
use function stream_copy_to_stream;
use function stream_get_meta_data;
use function tmpfile;
use const DB2_BINARY;
use const DB2_CHAR;
use const DB2_LONG;
use const DB2_PARAM_FILE;
use const DB2_PARAM_IN;
/**
* @deprecated Use {@link Statement} instead
*/
class DB2Statement implements StatementInterface
{
/** @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 = [];
/**
* @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($column, &$variable, $type = ParameterType::STRING, $length = null)
{
assert(is_int($column));
switch ($type) {
case ParameterType::INTEGER:
$this->bind($column, $variable, DB2_PARAM_IN, DB2_LONG);
break;
case ParameterType::LARGE_OBJECT:
if (isset($this->lobs[$column])) {
[, $handle] = $this->lobs[$column];
fclose($handle);
}
$handle = $this->createTemporaryFile();
$path = stream_get_meta_data($handle)['uri'];
$this->bind($column, $path, DB2_PARAM_FILE, DB2_BINARY);
$this->lobs[$column] = [&$variable, $handle];
break;
default:
$this->bind($column, $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}
*/
public function execute($params = null): ResultInterface
{
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);
}
$result = db2_execute($this->stmt, $params);
foreach ($this->lobs as [, $handle]) {
fclose($handle);
}
$this->lobs = [];
if ($result === false) {
throw StatementError::new($this->stmt);
}
return new Result($this->stmt);
}
/**
* @return resource
*
* @throws DB2Exception
*/
private function createTemporaryFile()
{
$handle = @tmpfile();
if ($handle === false) {
throw CannotCreateTemporaryFile::new(error_get_last()['message']);
}
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()['message']);
}
}
/**
* @param resource $target
*
* @throws DB2Exception
*/
private function writeStringToStream(string $string, $target): void
{
if (@fwrite($target, $string) === false) {
throw CannotWriteToTemporaryFile::new(error_get_last()['message']);
}
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2;
final class Driver extends DB2Driver
use Doctrine\DBAL\Driver\AbstractDB2Driver;
final class Driver extends AbstractDB2Driver
{
/**
* {@inheritdoc}
*/
public function connect(array $params)
{
return new Connection(
DataSourceName::fromConnectionParameters($params)->toString(),
isset($params['persistent']) && $params['persistent'] === true,
$params['user'] ?? '',
$params['password'] ?? '',
$params['driver_options'] ?? []
);
}
}
......@@ -4,14 +4,14 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\IBMDB2\DB2Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class CannotCopyStreamToStream extends DB2Exception
final class CannotCopyStreamToStream extends AbstractException
{
public static function new(string $message): self
{
......
......@@ -4,14 +4,14 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\IBMDB2\DB2Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class CannotCreateTemporaryFile extends DB2Exception
final class CannotCreateTemporaryFile extends AbstractException
{
public static function new(string $message): self
{
......
......@@ -4,14 +4,14 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\IBMDB2\DB2Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class CannotWriteToTemporaryFile extends DB2Exception
final class CannotWriteToTemporaryFile extends AbstractException
{
public static function new(string $message): self
{
......
......@@ -2,6 +2,197 @@
namespace Doctrine\DBAL\Driver\IBMDB2;
final class Statement extends DB2Statement
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCopyStreamToStream;
use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCreateTemporaryFile;
use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotWriteToTemporaryFile;
use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use function assert;
use function db2_bind_param;
use function db2_execute;
use function error_get_last;
use function fclose;
use function fwrite;
use function is_int;
use function is_resource;
use function ksort;
use function stream_copy_to_stream;
use function stream_get_meta_data;
use function tmpfile;
use const DB2_BINARY;
use const DB2_CHAR;
use const DB2_LONG;
use const DB2_PARAM_FILE;
use const DB2_PARAM_IN;
final class Statement implements StatementInterface
{
/** @var resource */
private $stmt;
/** @var mixed[] */
private $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 = [];
/**
* @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($column, &$variable, $type = ParameterType::STRING, $length = null)
{
assert(is_int($column));
switch ($type) {
case ParameterType::INTEGER:
$this->bind($column, $variable, DB2_PARAM_IN, DB2_LONG);
break;
case ParameterType::LARGE_OBJECT:
if (isset($this->lobs[$column])) {
[, $handle] = $this->lobs[$column];
fclose($handle);
}
$handle = $this->createTemporaryFile();
$path = stream_get_meta_data($handle)['uri'];
$this->bind($column, $path, DB2_PARAM_FILE, DB2_BINARY);
$this->lobs[$column] = [&$variable, $handle];
break;
default:
$this->bind($column, $variable, DB2_PARAM_IN, DB2_CHAR);
break;
}
return true;
}
/**
* @param int $position Parameter position
* @param mixed $variable
*
* @throws Exception
*/
private function bind($position, &$variable, int $parameterType, int $dataType): void
{
$this->bindParam[$position] =& $variable;
if (! db2_bind_param($this->stmt, $position, 'variable', $parameterType, $dataType)) {
throw StatementError::new($this->stmt);
}
}
/**
* {@inheritdoc}
*/
public function execute($params = null): ResultInterface
{
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);
}
$result = db2_execute($this->stmt, $params);
foreach ($this->lobs as [, $handle]) {
fclose($handle);
}
$this->lobs = [];
if ($result === false) {
throw StatementError::new($this->stmt);
}
return new Result($this->stmt);
}
/**
* @return resource
*
* @throws Exception
*/
private function createTemporaryFile()
{
$handle = @tmpfile();
if ($handle === false) {
throw CannotCreateTemporaryFile::new(error_get_last()['message']);
}
return $handle;
}
/**
* @param resource $source
* @param resource $target
*
* @throws Exception
*/
private function copyStreamToStream($source, $target): void
{
if (@stream_copy_to_stream($source, $target) === false) {
throw CannotCopyStreamToStream::new(error_get_last()['message']);
}
}
/**
* @param resource $target
*
* @throws Exception
*/
private function writeStringToStream(string $string, $target): void
{
if (@fwrite($target, $string) === false) {
throw CannotWriteToTemporaryFile::new(error_get_last()['message']);
}
}
}
......@@ -2,6 +2,168 @@
namespace Doctrine\DBAL\Driver\Mysqli;
final class Connection extends MysqliConnection
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError;
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionFailed;
use Doctrine\DBAL\Driver\PingableConnection;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use mysqli;
use function floor;
use function mysqli_init;
use function stripos;
final class Connection implements PingableConnection, ServerInfoAwareConnection
{
/**
* Name of the option to set connection flags
*/
public const OPTION_FLAGS = 'flags';
/** @var mysqli */
private $conn;
/**
* @param iterable<Initializer> $preInitializers
* @param iterable<Initializer> $postInitializers
*
* @throws Exception
*/
public function __construct(
?string $host = null,
?string $username = null,
?string $password = null,
?string $database = null,
?int $port = null,
?string $socket = null,
?int $flags = null,
iterable $preInitializers = [],
iterable $postInitializers = []
) {
$connection = mysqli_init();
foreach ($preInitializers as $initializer) {
$initializer->initialize($connection);
}
if (! @$connection->real_connect($host, $username, $password, $database, $port, $socket, $flags)) {
throw ConnectionFailed::new($connection);
}
foreach ($postInitializers as $initializer) {
$initializer->initialize($connection);
}
$this->conn = $connection;
}
/**
* 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;
}
public function prepare(string $sql): DriverStatement
{
return new Statement($this->conn, $sql);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritdoc}
*/
public function quote($input, $type = ParameterType::STRING)
{
return "'" . $this->conn->escape_string($input) . "'";
}
public function exec(string $statement): int
{
if ($this->conn->query($statement) === 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()
{
return $this->conn->commit();
}
/**
* {@inheritdoc}non-PHPdoc)
*/
public function rollBack()
{
return $this->conn->rollback();
}
/**
* Pings the server and re-connects when `mysqli.reconnect = 1`
*
* @deprecated
*
* @return bool
*/
public function ping()
{
return $this->conn->ping();
}
}
......@@ -3,6 +3,7 @@
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\AbstractMySQLDriver;
use Doctrine\DBAL\Driver\Mysqli\Exception\HostRequired;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Charset;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Options;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Secure;
......@@ -33,9 +34,9 @@ class Driver extends AbstractMySQLDriver
if (isset($params['driver_options'])) {
$driverOptions = $params['driver_options'];
if (isset($driverOptions[MysqliConnection::OPTION_FLAGS])) {
$flags = $driverOptions[MysqliConnection::OPTION_FLAGS];
unset($driverOptions[MysqliConnection::OPTION_FLAGS]);
if (isset($driverOptions[Connection::OPTION_FLAGS])) {
$flags = $driverOptions[Connection::OPTION_FLAGS];
unset($driverOptions[Connection::OPTION_FLAGS]);
}
$preInitializers = $this->withOptions($preInitializers, $driverOptions);
......
......@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli;
/**
......@@ -12,7 +12,7 @@ use mysqli;
*
* @psalm-immutable
*/
final class ConnectionError extends MysqliException
final class ConnectionError extends AbstractException
{
public static function new(mysqli $connection): self
{
......
......@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli;
/**
......@@ -12,7 +12,7 @@ use mysqli;
*
* @psalm-immutable
*/
final class ConnectionFailed extends MysqliException
final class ConnectionFailed extends AbstractException
{
public static function new(mysqli $connection): self
{
......
......@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
......@@ -13,7 +13,7 @@ use function sprintf;
*
* @psalm-immutable
*/
final class FailedReadingStreamOffset extends MysqliException
final class FailedReadingStreamOffset extends AbstractException
{
public static function new(int $offset): self
{
......
......@@ -2,14 +2,16 @@
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli;
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class HostRequired extends MysqliException
final class HostRequired extends AbstractException
{
public static function forPersistentConnection(): self
{
......
......@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli;
use function sprintf;
......@@ -14,7 +14,7 @@ use function sprintf;
*
* @psalm-immutable
*/
final class InvalidCharset extends MysqliException
final class InvalidCharset extends AbstractException
{
public static function fromCharset(mysqli $connection, string $charset): self
{
......
......@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
......@@ -13,7 +13,7 @@ use function sprintf;
*
* @psalm-immutable
*/
final class InvalidOption extends MysqliException
final class InvalidOption extends AbstractException
{
/**
* @param mixed $value
......
......@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli_stmt;
/**
......@@ -12,7 +12,7 @@ use mysqli_stmt;
*
* @psalm-immutable
*/
final class StatementError extends MysqliException
final class StatementError extends AbstractException
{
public static function new(mysqli_stmt $statement): self
{
......
......@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
......@@ -13,7 +13,7 @@ use function sprintf;
*
* @psalm-immutable
*/
final class UnknownType extends MysqliException
final class UnknownType extends AbstractException
{
/**
* @param mixed $type
......
......@@ -4,12 +4,13 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Exception;
use mysqli;
interface Initializer
{
/**
* @throws MysqliException
* @throws Exception
*/
public function initialize(mysqli $connection): void;
}
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError;
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionFailed;
use Doctrine\DBAL\Driver\PingableConnection;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use mysqli;
use function floor;
use function mysqli_init;
use function stripos;
/**
* @deprecated Use {@link Connection} instead
*/
class MysqliConnection implements PingableConnection, ServerInfoAwareConnection
{
/**
* Name of the option to set connection flags
*/
public const OPTION_FLAGS = 'flags';
/** @var mysqli */
private $conn;
/**
* @param iterable<Initializer> $preInitializers
* @param iterable<Initializer> $postInitializers
*
* @throws MysqliException
*/
public function __construct(
?string $host = null,
?string $username = null,
?string $password = null,
?string $database = null,
?int $port = null,
?string $socket = null,
?int $flags = null,
iterable $preInitializers = [],
iterable $postInitializers = []
) {
$connection = mysqli_init();
foreach ($preInitializers as $initializer) {
$initializer->initialize($connection);
}
if (! @$connection->real_connect($host, $username, $password, $database, $port, $socket, $flags)) {
throw ConnectionFailed::new($connection);
}
foreach ($postInitializers as $initializer) {
$initializer->initialize($connection);
}
$this->conn = $connection;
}
/**
* 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;
}
public function prepare(string $sql): DriverStatement
{
return new Statement($this->conn, $sql);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritdoc}
*/
public function quote($input, $type = ParameterType::STRING)
{
return "'" . $this->conn->escape_string($input) . "'";
}
public function exec(string $statement): int
{
if ($this->conn->query($statement) === 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()
{
return $this->conn->commit();
}
/**
* {@inheritdoc}non-PHPdoc)
*/
public function rollBack()
{
return $this->conn->rollback();
}
/**
* Pings the server and re-connects when `mysqli.reconnect = 1`
*
* @deprecated
*
* @return bool
*/
public function ping()
{
return $this->conn->ping();
}
}
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\AbstractDriverException;
/**
* @deprecated Use {@link Exception} instead
*
* @psalm-immutable
*/
class MysqliException extends AbstractDriverException
{
}
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
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 as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\DBAL\ParameterType;
use mysqli;
use mysqli_stmt;
use function array_fill;
use function assert;
use function count;
use function feof;
use function fread;
use function get_resource_type;
use function is_int;
use function is_resource;
use function str_repeat;
/**
* @deprecated Use {@link Statement} instead
*/
class MysqliStatement implements StatementInterface
{
/** @var string[] */
protected static $_paramTypeMap = [
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 mixed[] */
protected $_bindedValues;
/** @var string */
protected $types;
/**
* Contains ref values for bindValue().
*
* @var mixed[]
*/
protected $_values = [];
/**
* @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;
$stmt = $conn->prepare($prepareString);
if ($stmt === false) {
throw ConnectionError::new($this->_conn);
}
$this->_stmt = $stmt;
$paramCount = $this->_stmt->param_count;
if (0 >= $paramCount) {
return;
}
$this->types = str_repeat('s', $paramCount);
$this->_bindedValues = array_fill(1, $paramCount, null);
}
/**
* {@inheritdoc}
*/
public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
{
assert(is_int($column));
if (! isset(self::$_paramTypeMap[$type])) {
throw UnknownType::new($type);
}
$this->_bindedValues[$column] =& $variable;
$this->types[$column - 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): ResultInterface
{
if ($this->_bindedValues !== null) {
if ($params !== null) {
if (! $this->bindUntypedValues($params)) {
throw StatementError::new($this->_stmt);
}
} else {
$this->bindTypedParameters();
}
}
if (! $this->_stmt->execute()) {
throw StatementError::new($this->_stmt);
}
return new Result($this->_stmt);
}
/**
* 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);
}
}
......@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
......@@ -42,7 +43,7 @@ final class Result implements ResultInterface
private $boundValues = [];
/**
* @throws MysqliException
* @throws Exception
*/
public function __construct(mysqli_stmt $statement)
{
......
......@@ -2,6 +2,223 @@
namespace Doctrine\DBAL\Driver\Mysqli;
final class Statement extends MysqliStatement
use Doctrine\DBAL\Driver\Exception;
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 as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\DBAL\ParameterType;
use mysqli;
use mysqli_stmt;
use function array_fill;
use function assert;
use function count;
use function feof;
use function fread;
use function get_resource_type;
use function is_int;
use function is_resource;
use function str_repeat;
final class Statement implements StatementInterface
{
/** @var string[] */
protected static $_paramTypeMap = [
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 mixed[] */
protected $_bindedValues;
/** @var string */
protected $types;
/**
* Contains ref values for bindValue().
*
* @var mixed[]
*/
protected $_values = [];
/**
* @internal The statement can be only instantiated by its driver connection.
*
* @param string $prepareString
*
* @throws Exception
*/
public function __construct(mysqli $conn, $prepareString)
{
$this->_conn = $conn;
$stmt = $conn->prepare($prepareString);
if ($stmt === false) {
throw ConnectionError::new($this->_conn);
}
$this->_stmt = $stmt;
$paramCount = $this->_stmt->param_count;
if (0 >= $paramCount) {
return;
}
$this->types = str_repeat('s', $paramCount);
$this->_bindedValues = array_fill(1, $paramCount, null);
}
/**
* {@inheritdoc}
*/
public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
{
assert(is_int($column));
if (! isset(self::$_paramTypeMap[$type])) {
throw UnknownType::new($type);
}
$this->_bindedValues[$column] =& $variable;
$this->types[$column - 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): ResultInterface
{
if ($this->_bindedValues !== null) {
if ($params !== null) {
if (! $this->bindUntypedValues($params)) {
throw StatementError::new($this->_stmt);
}
} else {
$this->bindTypedParameters();
}
}
if (! $this->_stmt->execute()) {
throw StatementError::new($this->_stmt);
}
return new Result($this->_stmt);
}
/**
* 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 Exception
*/
private function sendLongData(array $streams): void
{
foreach ($streams as $paramNr => $stream) {
while (! feof($stream)) {
$chunk = fread($stream, 8192);
if ($chunk === false) {
throw FailedReadingStreamOffset::new($paramNr);
}
if (! $this->_stmt->send_long_data($paramNr - 1, $chunk)) {
throw StatementError::new($this->_stmt);
}
}
}
}
/**
* Binds a array of values to bound parameters.
*
* @param mixed[] $values
*
* @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);
}
}
......@@ -2,6 +2,182 @@
namespace Doctrine\DBAL\Driver\OCI8;
final class Connection extends OCI8Connection
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\OCI8\Exception\ConnectionFailed;
use Doctrine\DBAL\Driver\OCI8\Exception\Error;
use Doctrine\DBAL\Driver\OCI8\Exception\SequenceDoesNotExist;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use UnexpectedValueException;
use function addcslashes;
use function is_float;
use function is_int;
use function oci_commit;
use function oci_connect;
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_NO_AUTO_COMMIT;
final class Connection implements ConnectionInterface, ServerInfoAwareConnection
{
/** @var resource */
protected $dbh;
/** @var ExecutionMode */
private $executionMode;
/**
* Creates a Connection to an Oracle Database using oci8 extension.
*
* @param string $username
* @param string $password
* @param string $db
* @param string $charset
* @param int $sessionMode
* @param bool $persistent
*
* @throws Exception
*/
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 ConnectionFailed::new();
}
$this->dbh = $dbh;
$this->executionMode = new ExecutionMode();
}
/**
* {@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 Error::new($this->dbh);
}
if (preg_match('/\s+(\d+\.\d+\.\d+\.\d+\.\d+)\s+/', $version, $matches) === 0) {
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];
}
public function prepare(string $sql): DriverStatement
{
return new Statement($this->dbh, $sql, $this->executionMode);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritdoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
if (is_int($value) || is_float($value)) {
return $value;
}
$value = str_replace("'", "''", $value);
return "'" . addcslashes($value, "\000\n\r\\\032") . "'";
}
public function exec(string $statement): int
{
return $this->prepare($statement)->execute()->rowCount();
}
/**
* {@inheritdoc}
*
* @return int|false
*/
public function lastInsertId($name = null)
{
if ($name === null) {
return false;
}
$result = $this->query('SELECT ' . $name . '.CURRVAL FROM DUAL')->fetchOne();
if ($result === false) {
throw SequenceDoesNotExist::new();
}
return (int) $result;
}
/**
* {@inheritdoc}
*/
public function beginTransaction()
{
$this->executionMode->disableAutoCommit();
return true;
}
/**
* {@inheritdoc}
*/
public function commit()
{
if (! oci_commit($this->dbh)) {
throw Error::new($this->dbh);
}
$this->executionMode->enableAutoCommit();
return true;
}
/**
* {@inheritdoc}
*/
public function rollBack()
{
if (! oci_rollback($this->dbh)) {
throw Error::new($this->dbh);
}
$this->executionMode->enableAutoCommit();
return true;
}
}
......@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\OCI8\Exception\NonTerminatedStringLiteral;
use function count;
......@@ -31,7 +32,7 @@ final class ConvertPositionalToNamedPlaceholders
*
* @return mixed[] [0] => the statement value (string), [1] => the paramMap value (array).
*
* @throws OCI8Exception
* @throws Exception
*/
public function __invoke(string $statement): array
{
......
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function assert;
use function oci_error;
/**
* @internal
*
* @psalm-immutable
*/
final class ConnectionFailed extends AbstractException
{
public static function new(): self
{
$error = oci_error();
assert($error !== false);
return new self($error['message'], null, $error['code']);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function assert;
use function oci_error;
/**
* @internal
*
* @psalm-immutable
*/
final class Error extends AbstractException
{
/**
* @param resource $resource
*/
public static function new($resource): self
{
$error = oci_error($resource);
assert($error !== false);
return new self($error['message'], null, $error['code']);
}
}
......@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\OCI8\OCI8Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
......@@ -13,7 +13,7 @@ use function sprintf;
*
* @psalm-immutable
*/
final class NonTerminatedStringLiteral extends OCI8Exception
final class NonTerminatedStringLiteral extends AbstractException
{
public static function new(int $offset): self
{
......
......@@ -4,14 +4,14 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\OCI8\OCI8Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class SequenceDoesNotExist extends OCI8Exception
final class SequenceDoesNotExist extends AbstractException
{
public static function new(): self
{
......
......@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\OCI8\OCI8Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
......@@ -13,7 +13,7 @@ use function sprintf;
*
* @psalm-immutable
*/
final class UnknownParameterIndex extends OCI8Exception
final class UnknownParameterIndex extends AbstractException
{
public static function new(int $index): self
{
......
<?php
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
use Doctrine\DBAL\Driver\OCI8\Exception\SequenceDoesNotExist;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use UnexpectedValueException;
use function addcslashes;
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_NO_AUTO_COMMIT;
/**
* OCI8 implementation of the Connection interface.
*
* @deprecated Use {@link Connection} instead
*/
class OCI8Connection implements ConnectionInterface, ServerInfoAwareConnection
{
/** @var resource */
protected $dbh;
/** @var ExecutionMode */
private $executionMode;
/**
* Creates a Connection to an Oracle Database using oci8 extension.
*
* @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;
$this->executionMode = new ExecutionMode();
}
/**
* {@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) === 0) {
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];
}
public function prepare(string $sql): DriverStatement
{
return new Statement($this->dbh, $sql, $this->executionMode);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritdoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
if (is_int($value) || is_float($value)) {
return $value;
}
$value = str_replace("'", "''", $value);
return "'" . addcslashes($value, "\000\n\r\\\032") . "'";
}
public function exec(string $statement): int
{
return $this->prepare($statement)->execute()->rowCount();
}
/**
* {@inheritdoc}
*
* @return int|false
*/
public function lastInsertId($name = null)
{
if ($name === null) {
return false;
}
$result = $this->query('SELECT ' . $name . '.CURRVAL FROM DUAL')->fetchOne();
if ($result === false) {
throw SequenceDoesNotExist::new();
}
return (int) $result;
}
/**
* {@inheritdoc}
*/
public function beginTransaction()
{
$this->executionMode->disableAutoCommit();
return true;
}
/**
* {@inheritdoc}
*/
public function commit()
{
if (! oci_commit($this->dbh)) {
throw OCI8Exception::fromErrorInfo(oci_error($this->dbh));
}
$this->executionMode->enableAutoCommit();
return true;
}
/**
* {@inheritdoc}
*/
public function rollBack()
{
if (! oci_rollback($this->dbh)) {
throw OCI8Exception::fromErrorInfo(oci_error($this->dbh));
}
$this->executionMode->enableAutoCommit();
return true;
}
}
<?php
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\AbstractDriverException;
/**
* @deprecated Use {@link 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']);
}
}
<?php
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\OCI8\Exception\UnknownParameterIndex;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use function assert;
use function is_int;
use function is_resource;
use function oci_bind_by_name;
use function oci_error;
use function oci_execute;
use function oci_new_descriptor;
use function oci_parse;
use const OCI_B_BIN;
use const OCI_B_BLOB;
use const OCI_COMMIT_ON_SUCCESS;
use const OCI_D_LOB;
use const OCI_NO_AUTO_COMMIT;
use const OCI_TEMP_BLOB;
use const SQLT_CHR;
/**
* The OCI8 implementation of the Statement interface.
*
* @deprecated Use {@link Statement} instead
*/
class OCI8Statement implements StatementInterface
{
/** @var resource */
protected $_dbh;
/** @var resource */
protected $_sth;
/** @var ExecutionMode */
private $executionMode;
/** @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 = [];
/**
* 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, ExecutionMode $executionMode)
{
[$query, $paramMap] = (new ConvertPositionalToNamedPlaceholders())($query);
$stmt = oci_parse($dbh, $query);
assert(is_resource($stmt));
$this->_sth = $stmt;
$this->_dbh = $dbh;
$this->_paramMap = $paramMap;
$this->executionMode = $executionMode;
}
/**
* {@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);
$class = 'OCI-Lob';
assert($lob instanceof $class);
$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}
*/
public function execute($params = null): ResultInterface
{
if ($params !== null) {
foreach ($params as $key => $val) {
if (is_int($key)) {
$this->bindValue($key + 1, $val);
} else {
$this->bindValue($key, $val);
}
}
}
if ($this->executionMode->isAutoCommitEnabled()) {
$mode = OCI_COMMIT_ON_SUCCESS;
} else {
$mode = OCI_NO_AUTO_COMMIT;
}
$ret = @oci_execute($this->_sth, $mode);
if (! $ret) {
throw OCI8Exception::fromErrorInfo(oci_error($this->_sth));
}
return new Result($this->_sth);
}
}
......@@ -2,6 +2,158 @@
namespace Doctrine\DBAL\Driver\OCI8;
final class Statement extends OCI8Statement
use Doctrine\DBAL\Driver\OCI8\Exception\Error;
use Doctrine\DBAL\Driver\OCI8\Exception\UnknownParameterIndex;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use function assert;
use function is_int;
use function is_resource;
use function oci_bind_by_name;
use function oci_execute;
use function oci_new_descriptor;
use function oci_parse;
use const OCI_B_BIN;
use const OCI_B_BLOB;
use const OCI_COMMIT_ON_SUCCESS;
use const OCI_D_LOB;
use const OCI_NO_AUTO_COMMIT;
use const OCI_TEMP_BLOB;
use const SQLT_CHR;
final class Statement implements StatementInterface
{
/** @var resource */
protected $_dbh;
/** @var resource */
protected $_sth;
/** @var ExecutionMode */
private $executionMode;
/** @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 = [];
/**
* 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, ExecutionMode $executionMode)
{
[$query, $paramMap] = (new ConvertPositionalToNamedPlaceholders())($query);
$stmt = oci_parse($dbh, $query);
assert(is_resource($stmt));
$this->_sth = $stmt;
$this->_dbh = $dbh;
$this->_paramMap = $paramMap;
$this->executionMode = $executionMode;
}
/**
* {@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);
$class = 'OCI-Lob';
assert($lob instanceof $class);
$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}
*/
public function execute($params = null): ResultInterface
{
if ($params !== null) {
foreach ($params as $key => $val) {
if (is_int($key)) {
$this->bindValue($key + 1, $val);
} else {
$this->bindValue($key, $val);
}
}
}
if ($this->executionMode->isAutoCommitEnabled()) {
$mode = OCI_COMMIT_ON_SUCCESS;
} else {
$mode = OCI_NO_AUTO_COMMIT;
}
$ret = @oci_execute($this->_sth, $mode);
if (! $ret) {
throw Error::new($this->_sth);
}
return new Result($this->_sth);
}
}
......@@ -2,8 +2,142 @@
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\PDOConnection;
use Doctrine\DBAL\Driver\Exception as ExceptionInterface;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use PDO;
use PDOException;
use PDOStatement;
class Connection extends PDOConnection
use function assert;
class Connection implements ServerInfoAwareConnection
{
/** @var PDO */
private $connection;
/**
* @param string $dsn
* @param string|null $user
* @param string|null $password
* @param mixed[]|null $options
*
* @throws ExceptionInterface
*/
public function __construct($dsn, $user = null, $password = null, ?array $options = null)
{
try {
$this->connection = new PDO($dsn, (string) $user, (string) $password, (array) $options);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
public function exec(string $statement): int
{
try {
$result = $this->connection->exec($statement);
assert($result !== false);
return $result;
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*/
public function getServerVersion()
{
return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION);
}
public function prepare(string $sql): StatementInterface
{
try {
return $this->createStatement(
$this->connection->prepare($sql)
);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
public function query(string $sql): ResultInterface
{
try {
$stmt = $this->connection->query($sql);
assert($stmt instanceof PDOStatement);
return new Result($stmt);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*/
public function quote($input, $type = ParameterType::STRING)
{
return $this->connection->quote($input, $type);
}
/**
* {@inheritdoc}
*/
public function lastInsertId($name = null)
{
try {
if ($name === null) {
return $this->connection->lastInsertId();
}
return $this->connection->lastInsertId($name);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* Creates a wrapped statement
*/
protected function createStatement(PDOStatement $stmt): Statement
{
return new Statement($stmt);
}
/**
* {@inheritDoc}
*/
public function beginTransaction()
{
return $this->connection->beginTransaction();
}
/**
* {@inheritDoc}
*/
public function commit()
{
return $this->connection->commit();
}
/**
* {@inheritDoc}
*/
public function rollBack()
{
return $this->connection->rollBack();
}
public function getWrappedConnection(): PDO
{
return $this->connection;
}
}
......@@ -4,17 +4,25 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\PDOException;
use Doctrine\DBAL\Driver\AbstractException;
use PDOException;
/**
* @internal
*
* @psalm-immutable
*/
final class Exception extends PDOException
final class Exception extends AbstractException
{
public static function new(\PDOException $exception): self
public static function new(PDOException $exception): self
{
return new self($exception);
if ($exception->errorInfo !== null) {
[$sqlState, $code] = $exception->errorInfo;
} else {
$code = $exception->getCode();
$sqlState = null;
}
return new self($exception->getMessage(), $sqlState, $code, $exception);
}
}
......@@ -2,8 +2,98 @@
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\PDOStatement;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use InvalidArgumentException;
use PDO;
use PDOException;
use PDOStatement;
class Statement extends PDOStatement
use function array_slice;
use function func_get_args;
class Statement implements StatementInterface
{
private const PARAM_TYPE_MAP = [
ParameterType::NULL => PDO::PARAM_NULL,
ParameterType::INTEGER => PDO::PARAM_INT,
ParameterType::STRING => PDO::PARAM_STR,
ParameterType::BINARY => PDO::PARAM_LOB,
ParameterType::LARGE_OBJECT => PDO::PARAM_LOB,
ParameterType::BOOLEAN => PDO::PARAM_BOOL,
];
/** @var PDOStatement */
private $stmt;
/**
* @internal The statement can be only instantiated by its driver connection.
*/
public function __construct(PDOStatement $stmt)
{
$this->stmt = $stmt;
}
/**
* {@inheritdoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
$type = $this->convertParamType($type);
try {
return $this->stmt->bindValue($param, $value, $type);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* @param mixed $column
* @param mixed $variable
* @param int $type
* @param int|null $length
* @param mixed $driverOptions
*
* @return bool
*/
public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null)
{
$type = $this->convertParamType($type);
try {
return $this->stmt->bindParam($column, $variable, $type, ...array_slice(func_get_args(), 3));
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*/
public function execute($params = null): ResultInterface
{
try {
$this->stmt->execute($params);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
return new Result($this->stmt);
}
/**
* 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])) {
throw new InvalidArgumentException('Invalid parameter type: ' . $type);
}
return self::PARAM_TYPE_MAP[$type];
}
}
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\DBAL\Driver\PDO\Result;
use Doctrine\DBAL\Driver\PDO\Statement;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use PDO;
use PDOException;
use PDOStatement;
use function assert;
/**
* PDO implementation of the Connection interface.
*
* Used by all PDO-based drivers.
*
* @deprecated Use {@link Connection} instead
*/
class PDOConnection implements ServerInfoAwareConnection
{
/** @var PDO */
private $connection;
/**
* @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 {
$this->connection = new PDO($dsn, (string) $user, (string) $password, (array) $options);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
public function exec(string $statement): int
{
try {
$result = $this->connection->exec($statement);
assert($result !== false);
return $result;
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*/
public function getServerVersion()
{
return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION);
}
public function prepare(string $sql): StatementInterface
{
try {
return $this->createStatement(
$this->connection->prepare($sql)
);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
public function query(string $sql): ResultInterface
{
try {
$stmt = $this->connection->query($sql);
assert($stmt instanceof PDOStatement);
return new Result($stmt);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*/
public function quote($input, $type = ParameterType::STRING)
{
return $this->connection->quote($input, $type);
}
/**
* {@inheritdoc}
*/
public function lastInsertId($name = null)
{
try {
if ($name === null) {
return $this->connection->lastInsertId();
}
return $this->connection->lastInsertId($name);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* Creates a wrapped statement
*/
protected function createStatement(PDOStatement $stmt): Statement
{
return new Statement($stmt);
}
/**
* {@inheritDoc}
*/
public function beginTransaction()
{
return $this->connection->beginTransaction();
}
/**
* {@inheritDoc}
*/
public function commit()
{
return $this->connection->commit();
}
/**
* {@inheritDoc}
*/
public function rollBack()
{
return $this->connection->rollBack();
}
public function getWrappedConnection(): PDO
{
return $this->connection;
}
}
<?php
namespace Doctrine\DBAL\Driver;
/**
* @deprecated Use {@link Exception} instead
*
* @psalm-immutable
*/
class PDOException extends AbstractDriverException
{
/**
* @param \PDOException $exception The PDO exception to wrap.
*/
public function __construct(\PDOException $exception)
{
if ($exception->errorInfo !== null) {
[$sqlState, $code] = $exception->errorInfo;
} else {
$code = $exception->getCode();
$sqlState = null;
}
parent::__construct($exception->getMessage(), $sqlState, $code, $exception);
}
}
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\DBAL\Driver\PDO\Result;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use InvalidArgumentException;
use PDO;
use PDOException;
use function array_slice;
use function func_get_args;
/**
* The PDO implementation of the Statement interface.
* Used by all PDO-based drivers.
*
* @deprecated Use {@link Statement} instead
*/
class PDOStatement implements StatementInterface
{
private const PARAM_TYPE_MAP = [
ParameterType::NULL => PDO::PARAM_NULL,
ParameterType::INTEGER => PDO::PARAM_INT,
ParameterType::STRING => PDO::PARAM_STR,
ParameterType::BINARY => PDO::PARAM_LOB,
ParameterType::LARGE_OBJECT => PDO::PARAM_LOB,
ParameterType::BOOLEAN => PDO::PARAM_BOOL,
];
/** @var \PDOStatement */
private $stmt;
/**
* @internal The statement can be only instantiated by its driver connection.
*/
public function __construct(\PDOStatement $stmt)
{
$this->stmt = $stmt;
}
/**
* {@inheritdoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
$type = $this->convertParamType($type);
try {
return $this->stmt->bindValue($param, $value, $type);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* @param mixed $column
* @param mixed $variable
* @param int $type
* @param int|null $length
* @param mixed $driverOptions
*
* @return bool
*/
public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null)
{
$type = $this->convertParamType($type);
try {
return $this->stmt->bindParam($column, $variable, $type, ...array_slice(func_get_args(), 3));
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritdoc}
*/
public function execute($params = null): ResultInterface
{
try {
$this->stmt->execute($params);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
return new Result($this->stmt);
}
/**
* 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])) {
throw new InvalidArgumentException('Invalid parameter type: ' . $type);
}
return self::PARAM_TYPE_MAP[$type];
}
}
......@@ -2,6 +2,157 @@
namespace Doctrine\DBAL\Driver\SQLSrv;
final class Connection extends SQLSrvConnection
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\SQLSrv\Exception\Error;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use function is_float;
use function is_int;
use function sprintf;
use function sqlsrv_begin_transaction;
use function sqlsrv_commit;
use function sqlsrv_configure;
use function sqlsrv_connect;
use function sqlsrv_query;
use function sqlsrv_rollback;
use function sqlsrv_rows_affected;
use function sqlsrv_server_info;
use function str_replace;
final class Connection implements ServerInfoAwareConnection
{
/** @var resource */
protected $conn;
/** @var LastInsertId */
protected $lastInsertId;
/**
* @param string $serverName
* @param mixed[] $connectionOptions
*
* @throws Exception
*/
public function __construct($serverName, $connectionOptions)
{
if (! sqlsrv_configure('WarningsReturnAsErrors', 0)) {
throw Error::new();
}
$conn = sqlsrv_connect($serverName, $connectionOptions);
if ($conn === false) {
throw Error::new();
}
$this->conn = $conn;
$this->lastInsertId = new LastInsertId();
}
/**
* {@inheritdoc}
*/
public function getServerVersion()
{
$serverInfo = sqlsrv_server_info($this->conn);
return $serverInfo['SQLServerVersion'];
}
public function prepare(string $sql): DriverStatement
{
return new Statement($this->conn, $sql, $this->lastInsertId);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritDoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
if (is_int($value)) {
return $value;
}
if (is_float($value)) {
return sprintf('%F', $value);
}
return "'" . str_replace("'", "''", $value) . "'";
}
public function exec(string $statement): int
{
$stmt = sqlsrv_query($this->conn, $statement);
if ($stmt === false) {
throw Error::new();
}
$rowsAffected = sqlsrv_rows_affected($stmt);
if ($rowsAffected === false) {
throw Error::new();
}
return $rowsAffected;
}
/**
* {@inheritDoc}
*/
public function lastInsertId($name = null)
{
if ($name !== null) {
$result = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?')
->execute([$name]);
} else {
$result = $this->query('SELECT @@IDENTITY');
}
return $result->fetchOne();
}
/**
* {@inheritDoc}
*/
public function beginTransaction()
{
if (! sqlsrv_begin_transaction($this->conn)) {
throw Error::new();
}
return true;
}
/**
* {@inheritDoc}
*/
public function commit()
{
if (! sqlsrv_commit($this->conn)) {
throw Error::new();
}
return true;
}
/**
* {@inheritDoc}
*/
public function rollBack()
{
if (! sqlsrv_rollback($this->conn)) {
throw Error::new();
}
return true;
}
}
......@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver\SQLSrv\Exception;
use Doctrine\DBAL\Driver\SQLSrv\SQLSrvException;
use Doctrine\DBAL\Driver\AbstractException;
use function rtrim;
use function sqlsrv_errors;
......@@ -16,7 +16,7 @@ use const SQLSRV_ERR_ERRORS;
*
* @psalm-immutable
*/
final class Error extends SQLSrvException
final class Error extends AbstractException
{
public static function new(): self
{
......
<?php
namespace Doctrine\DBAL\Driver\SQLSrv;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\SQLSrv\Exception\Error;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use function is_float;
use function is_int;
use function sprintf;
use function sqlsrv_begin_transaction;
use function sqlsrv_commit;
use function sqlsrv_configure;
use function sqlsrv_connect;
use function sqlsrv_query;
use function sqlsrv_rollback;
use function sqlsrv_rows_affected;
use function sqlsrv_server_info;
use function str_replace;
/**
* SQL Server implementation for the Connection interface.
*
* @deprecated Use {@link Connection} instead
*/
class SQLSrvConnection implements ServerInfoAwareConnection
{
/** @var resource */
protected $conn;
/** @var LastInsertId */
protected $lastInsertId;
/**
* @param string $serverName
* @param mixed[] $connectionOptions
*
* @throws SQLSrvException
*/
public function __construct($serverName, $connectionOptions)
{
if (! sqlsrv_configure('WarningsReturnAsErrors', 0)) {
throw Error::new();
}
$conn = sqlsrv_connect($serverName, $connectionOptions);
if ($conn === false) {
throw Error::new();
}
$this->conn = $conn;
$this->lastInsertId = new LastInsertId();
}
/**
* {@inheritdoc}
*/
public function getServerVersion()
{
$serverInfo = sqlsrv_server_info($this->conn);
return $serverInfo['SQLServerVersion'];
}
public function prepare(string $sql): DriverStatement
{
return new Statement($this->conn, $sql, $this->lastInsertId);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritDoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
if (is_int($value)) {
return $value;
}
if (is_float($value)) {
return sprintf('%F', $value);
}
return "'" . str_replace("'", "''", $value) . "'";
}
public function exec(string $statement): int
{
$stmt = sqlsrv_query($this->conn, $statement);
if ($stmt === false) {
throw Error::new();
}
$rowsAffected = sqlsrv_rows_affected($stmt);
if ($rowsAffected === false) {
throw Error::new();
}
return $rowsAffected;
}
/**
* {@inheritDoc}
*/
public function lastInsertId($name = null)
{
if ($name !== null) {
$result = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?')
->execute([$name]);
} else {
$result = $this->query('SELECT @@IDENTITY');
}
return $result->fetchOne();
}
/**
* {@inheritDoc}
*/
public function beginTransaction()
{
if (! sqlsrv_begin_transaction($this->conn)) {
throw Error::new();
}
return true;
}
/**
* {@inheritDoc}
*/
public function commit()
{
if (! sqlsrv_commit($this->conn)) {
throw Error::new();
}
return true;
}
/**
* {@inheritDoc}
*/
public function rollBack()
{
if (! sqlsrv_rollback($this->conn)) {
throw Error::new();
}
return true;
}
}
<?php
namespace Doctrine\DBAL\Driver\SQLSrv;
use Doctrine\DBAL\Driver\AbstractDriverException;
use Doctrine\DBAL\Driver\SQLSrv\Exception\Error;
/**
* @deprecated Use {@link Exception} instead
*
* @psalm-immutable
*/
class SQLSrvException extends AbstractDriverException
{
/**
* Helper method to turn sql server errors into exception.
*
* @return SQLSrvException
*/
public static function fromSqlSrvErrors()
{
return Error::new();
}
}
<?php
namespace Doctrine\DBAL\Driver\SQLSrv;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\SQLSrv\Exception\Error;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use function assert;
use function is_int;
use function sqlsrv_execute;
use function sqlsrv_fetch;
use function sqlsrv_get_field;
use function sqlsrv_next_result;
use function SQLSRV_PHPTYPE_STREAM;
use function SQLSRV_PHPTYPE_STRING;
use function sqlsrv_prepare;
use function SQLSRV_SQLTYPE_VARBINARY;
use function stripos;
use const SQLSRV_ENC_BINARY;
use const SQLSRV_PARAM_IN;
/**
* SQL Server Statement.
*
* @deprecated Use {@link Statement} instead
*/
class SQLSrvStatement implements StatementInterface
{
/**
* The SQLSRV Resource.
*
* @var resource
*/
private $conn;
/**
* The SQL statement to execute.
*
* @var string
*/
private $sql;
/**
* The SQLSRV statement resource.
*
* @var resource|null
*/
private $stmt;
/**
* References to the variables bound as statement parameters.
*
* @var mixed
*/
private $variables = [];
/**
* Bound parameter types.
*
* @var int[]
*/
private $types = [];
/**
* The last insert ID.
*
* @var LastInsertId|null
*/
private $lastInsertId;
/**
* Append to any INSERT query to retrieve the last insert id.
*/
private const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;';
/**
* @internal The statement can be only instantiated by its driver connection.
*
* @param resource $conn
* @param string $sql
*/
public function __construct($conn, $sql, ?LastInsertId $lastInsertId = null)
{
$this->conn = $conn;
$this->sql = $sql;
if (stripos($sql, 'INSERT INTO ') !== 0) {
return;
}
$this->sql .= self::LAST_INSERT_ID_SQL;
$this->lastInsertId = $lastInsertId;
}
/**
* {@inheritdoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
assert(is_int($param));
$this->variables[$param] = $value;
$this->types[$param] = $type;
return true;
}
/**
* {@inheritdoc}
*/
public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
{
assert(is_int($column));
$this->variables[$column] =& $variable;
$this->types[$column] = $type;
// unset the statement resource if it exists as the new one will need to be bound to the new variable
$this->stmt = null;
return true;
}
/**
* {@inheritdoc}
*/
public function execute($params = null): ResultInterface
{
if ($params !== null) {
foreach ($params as $key => $val) {
if (is_int($key)) {
$this->bindValue($key + 1, $val);
} else {
$this->bindValue($key, $val);
}
}
}
if ($this->stmt === null) {
$this->stmt = $this->prepare();
}
if (! sqlsrv_execute($this->stmt)) {
throw Error::new();
}
if ($this->lastInsertId !== null) {
sqlsrv_next_result($this->stmt);
sqlsrv_fetch($this->stmt);
$this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0));
}
return new Result($this->stmt);
}
/**
* Prepares SQL Server statement resource
*
* @return resource
*
* @throws SQLSrvException
*/
private function prepare()
{
$params = [];
foreach ($this->variables as $column => &$variable) {
switch ($this->types[$column]) {
case ParameterType::LARGE_OBJECT:
$params[$column - 1] = [
&$variable,
SQLSRV_PARAM_IN,
SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY),
SQLSRV_SQLTYPE_VARBINARY('max'),
];
break;
case ParameterType::BINARY:
$params[$column - 1] = [
&$variable,
SQLSRV_PARAM_IN,
SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY),
];
break;
default:
$params[$column - 1] =& $variable;
break;
}
}
$stmt = sqlsrv_prepare($this->conn, $this->sql, $params);
if ($stmt === false) {
throw Error::new();
}
return $stmt;
}
}
......@@ -2,6 +2,198 @@
namespace Doctrine\DBAL\Driver\SQLSrv;
final class Statement extends SQLSrvStatement
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\SQLSrv\Exception\Error;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use function assert;
use function is_int;
use function sqlsrv_execute;
use function sqlsrv_fetch;
use function sqlsrv_get_field;
use function sqlsrv_next_result;
use function SQLSRV_PHPTYPE_STREAM;
use function SQLSRV_PHPTYPE_STRING;
use function sqlsrv_prepare;
use function SQLSRV_SQLTYPE_VARBINARY;
use function stripos;
use const SQLSRV_ENC_BINARY;
use const SQLSRV_PARAM_IN;
final class Statement implements StatementInterface
{
/**
* The SQLSRV Resource.
*
* @var resource
*/
private $conn;
/**
* The SQL statement to execute.
*
* @var string
*/
private $sql;
/**
* The SQLSRV statement resource.
*
* @var resource|null
*/
private $stmt;
/**
* References to the variables bound as statement parameters.
*
* @var mixed
*/
private $variables = [];
/**
* Bound parameter types.
*
* @var int[]
*/
private $types = [];
/**
* The last insert ID.
*
* @var LastInsertId|null
*/
private $lastInsertId;
/**
* Append to any INSERT query to retrieve the last insert id.
*/
private const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;';
/**
* @internal The statement can be only instantiated by its driver connection.
*
* @param resource $conn
* @param string $sql
*/
public function __construct($conn, $sql, ?LastInsertId $lastInsertId = null)
{
$this->conn = $conn;
$this->sql = $sql;
if (stripos($sql, 'INSERT INTO ') !== 0) {
return;
}
$this->sql .= self::LAST_INSERT_ID_SQL;
$this->lastInsertId = $lastInsertId;
}
/**
* {@inheritdoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
assert(is_int($param));
$this->variables[$param] = $value;
$this->types[$param] = $type;
return true;
}
/**
* {@inheritdoc}
*/
public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
{
assert(is_int($column));
$this->variables[$column] =& $variable;
$this->types[$column] = $type;
// unset the statement resource if it exists as the new one will need to be bound to the new variable
$this->stmt = null;
return true;
}
/**
* {@inheritdoc}
*/
public function execute($params = null): ResultInterface
{
if ($params !== null) {
foreach ($params as $key => $val) {
if (is_int($key)) {
$this->bindValue($key + 1, $val);
} else {
$this->bindValue($key, $val);
}
}
}
if ($this->stmt === null) {
$this->stmt = $this->prepare();
}
if (! sqlsrv_execute($this->stmt)) {
throw Error::new();
}
if ($this->lastInsertId !== null) {
sqlsrv_next_result($this->stmt);
sqlsrv_fetch($this->stmt);
$this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0));
}
return new Result($this->stmt);
}
/**
* Prepares SQL Server statement resource
*
* @return resource
*
* @throws Exception
*/
private function prepare()
{
$params = [];
foreach ($this->variables as $column => &$variable) {
switch ($this->types[$column]) {
case ParameterType::LARGE_OBJECT:
$params[$column - 1] = [
&$variable,
SQLSRV_PARAM_IN,
SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY),
SQLSRV_SQLTYPE_VARBINARY('max'),
];
break;
case ParameterType::BINARY:
$params[$column - 1] = [
&$variable,
SQLSRV_PARAM_IN,
SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY),
];
break;
default:
$params[$column - 1] =& $variable;
break;
}
}
$stmt = sqlsrv_prepare($this->conn, $this->sql, $params);
if ($stmt === false) {
throw Error::new();
}
return $stmt;
}
}
......@@ -66,7 +66,7 @@ interface Statement
* @param mixed[]|null $params A numeric array of values with as many elements as there are
* bound parameters in the SQL statement being executed.
*
* @throws DriverException
* @throws Exception
*/
public function execute($params = null): Result;
}
......@@ -3,7 +3,7 @@
namespace Doctrine\DBAL\Exception;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException;
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use function assert;
......@@ -12,13 +12,13 @@ use function assert;
*
* @psalm-immutable
*/
class DriverException extends DBALException implements DeprecatedDriverException
class DriverException extends DBALException implements TheDriverException
{
/**
* @param string $message The exception message.
* @param DeprecatedDriverException $driverException The DBAL driver exception to chain.
* @param string $message The exception message.
* @param TheDriverException $driverException The DBAL driver exception to chain.
*/
public function __construct($message, DeprecatedDriverException $driverException)
public function __construct($message, TheDriverException $driverException)
{
parent::__construct($message, $driverException->getCode(), $driverException);
}
......@@ -29,7 +29,7 @@ class DriverException extends DBALException implements DeprecatedDriverException
public function getSQLState()
{
$previous = $this->getPrevious();
assert($previous instanceof DeprecatedDriverException);
assert($previous instanceof TheDriverException);
return $previous->getSQLState();
}
......
......@@ -9,7 +9,7 @@ use Doctrine\DBAL\ColumnCase;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Connection as BaseConnection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\PDOConnection;
use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection;
use Doctrine\DBAL\Driver\Result as DriverResult;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Result as DBALResult;
......
......@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL;
use Doctrine\DBAL\Abstraction\Result as ResultInterface;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Driver\Exception as DriverException;
use Doctrine\DBAL\Driver\Result as DriverResult;
use Traversable;
......
......@@ -4,7 +4,7 @@ namespace Doctrine\DBAL\Tests;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as InnerDriverException;
use Doctrine\DBAL\Driver\Exception as InnerDriverException;
use Doctrine\DBAL\Exception\DriverException;
use Exception;
use PHPUnit\Framework\TestCase;
......
......@@ -5,7 +5,7 @@ namespace Doctrine\DBAL\Tests\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\DriverException as DriverExceptionInterface;
use Doctrine\DBAL\Driver\Exception as DriverExceptionInterface;
use Doctrine\DBAL\Driver\ExceptionConverterDriver;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\ConstraintViolationException;
......
......@@ -3,12 +3,12 @@
namespace Doctrine\DBAL\Tests\Driver\Mysqli;
use Doctrine\DBAL\Driver\Mysqli\Driver;
use Doctrine\DBAL\Driver\Mysqli\HostRequired;
use Doctrine\DBAL\Driver\Mysqli\Exception\HostRequired;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use function extension_loaded;
class MysqliConnectionTest extends FunctionalTestCase
class ConnectionTest extends FunctionalTestCase
{
protected function setUp(): void
{
......
......@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\OCI8\ConvertPositionalToNamedPlaceholders;
use Doctrine\DBAL\Driver\OCI8\OCI8Exception;
use Doctrine\DBAL\Driver\OCI8\Exception\NonTerminatedStringLiteral;
use PHPUnit\Framework\TestCase;
class ConvertPositionalToNamedPlaceholdersTest extends TestCase
......@@ -95,7 +95,7 @@ class ConvertPositionalToNamedPlaceholdersTest extends TestCase
*/
public function testConvertNonTerminatedLiteral(string $sql, string $expectedExceptionMessageRegExp): void
{
$this->expectException(OCI8Exception::class);
$this->expectException(NonTerminatedStringLiteral::class);
$this->expectExceptionMessageMatches($expectedExceptionMessageRegExp);
($this->convertPositionalToNamedPlaceholders)($sql);
}
......
......@@ -40,7 +40,7 @@ class ExceptionTest extends TestCase
$this->wrappedException->errorInfo = [self::SQLSTATE, self::ERROR_CODE];
$this->exception = new Exception($this->wrappedException);
$this->exception = Exception::new($this->wrappedException);
}
public function testReturnsCode(): void
......
......@@ -4,7 +4,7 @@ namespace Doctrine\DBAL\Tests\Driver\PDOPgSql;
use Doctrine\DBAL\Driver as DriverInterface;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\PDOConnection;
use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection;
use Doctrine\DBAL\Driver\PDOPgSql\Driver;
use Doctrine\DBAL\Tests\Driver\AbstractPostgreSQLDriverTest;
use Doctrine\DBAL\Tests\TestUtil;
......
......@@ -5,7 +5,7 @@ namespace Doctrine\DBAL\Tests\Functional;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\PDOConnection;
use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
......
......@@ -11,7 +11,6 @@ use ReflectionProperty;
use function db2_close;
use function extension_loaded;
use function get_parent_class;
class ConnectionTest extends FunctionalTestCase
{
......@@ -45,7 +44,7 @@ class ConnectionTest extends FunctionalTestCase
{
$driverConnection = $this->connection->getWrappedConnection();
$re = new ReflectionProperty(get_parent_class($driverConnection), 'conn');
$re = new ReflectionProperty($driverConnection, 'conn');
$re->setAccessible(true);
$conn = $re->getValue($driverConnection);
db2_close($conn);
......
......@@ -2,8 +2,8 @@
namespace Doctrine\DBAL\Tests\Functional\Driver\IBMDB2;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\IBMDB2\DB2Driver;
use Doctrine\DBAL\Driver as DriverInterface;
use Doctrine\DBAL\Driver\IBMDB2\Driver;
use Doctrine\DBAL\Tests\Functional\Driver\AbstractDriverTest;
use function extension_loaded;
......@@ -18,7 +18,7 @@ class DriverTest extends AbstractDriverTest
parent::setUp();
if ($this->connection->getDriver() instanceof DB2Driver) {
if ($this->connection->getDriver() instanceof Driver) {
return;
}
......@@ -35,8 +35,8 @@ class DriverTest extends AbstractDriverTest
self::markTestSkipped('IBM DB2 does not support connecting without database name.');
}
protected function createDriver(): Driver
protected function createDriver(): DriverInterface
{
return new DB2Driver();
return new Driver();
}
}
......@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Tests\Functional\Driver\IBMDB2;
use Doctrine\DBAL\Driver\IBMDB2\DB2Driver;
use Doctrine\DBAL\Driver\IBMDB2\Driver;
use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError;
use Doctrine\DBAL\Tests\FunctionalTestCase;
......@@ -24,7 +24,7 @@ class StatementTest extends FunctionalTestCase
parent::setUp();
if ($this->connection->getDriver() instanceof DB2Driver) {
if ($this->connection->getDriver() instanceof Driver) {
return;
}
......
......@@ -2,9 +2,9 @@
namespace Doctrine\DBAL\Tests\Functional\Driver\Mysqli;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\Mysqli\Connection;
use Doctrine\DBAL\Driver\Mysqli\Driver;
use Doctrine\DBAL\Driver\Mysqli\MysqliConnection;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use Doctrine\DBAL\Tests\TestUtil;
......@@ -43,7 +43,7 @@ class ConnectionTest extends FunctionalTestCase
public function testUnsupportedDriverOption(): void
{
$this->expectException(MysqliException::class);
$this->expectException(Exception::class);
$this->getConnection([12345 => 'world']);
}
......@@ -52,7 +52,7 @@ class ConnectionTest extends FunctionalTestCase
{
$params = TestUtil::getConnectionParams();
$this->expectException(MysqliException::class);
$this->expectException(Exception::class);
(new Driver())->connect(
array_merge(
$params,
......@@ -70,7 +70,7 @@ class ConnectionTest extends FunctionalTestCase
/**
* @param mixed[] $driverOptions
*/
private function getConnection(array $driverOptions): MysqliConnection
private function getConnection(array $driverOptions): Connection
{
$params = TestUtil::getConnectionParams();
......
......@@ -2,8 +2,8 @@
namespace Doctrine\DBAL\Tests\Functional\Driver\OCI8;
use Doctrine\DBAL\Driver\OCI8\Connection;
use Doctrine\DBAL\Driver\OCI8\Driver;
use Doctrine\DBAL\Driver\OCI8\OCI8Connection;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;
......@@ -11,7 +11,7 @@ use function extension_loaded;
class ConnectionTest extends FunctionalTestCase
{
/** @var OCI8Connection */
/** @var Connection */
protected $driverConnection;
protected function setUp(): void
......
......@@ -2,8 +2,8 @@
namespace Doctrine\DBAL\Tests\Functional\Driver\SQLSrv;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\SQLSrv\Driver;
use Doctrine\DBAL\Driver\SQLSrv\SQLSrvException;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use function extension_loaded;
......@@ -32,7 +32,7 @@ class StatementTest extends FunctionalTestCase
// it's impossible to prepare the statement without bound variables for SQL Server,
// so the preparation happens before the first execution when variables are already in place
$this->expectException(SQLSrvException::class);
$this->expectException(Exception::class);
$stmt->execute();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment