class Connection in Drupal driver for SQL Server and SQL Azure 8.2
Same name in this branch
- 8.2 drivers/lib/Drupal/Driver/Database/sqlsrv/Connection.php \Drupal\Driver\Database\sqlsrv\Connection
- 8.2 drivers/lib/Drupal/Driver/Database/sqlsrv/PDO/Connection.php \Drupal\Driver\Database\sqlsrv\PDO\Connection
Hierarchy
- class \Drupal\Driver\Database\sqlsrv\PDO\Connection extends \PDO
Expanded class hierarchy of Connection
6 files declare their use of Connection
- Connection.php in drivers/
lib/ Drupal/ Driver/ Database/ sqlsrv/ Connection.php - Definition of Drupal\Driver\Database\sqlsrv\Connection
- DriverAttributes.php in drivers/
lib/ Drupal/ Driver/ Database/ sqlsrv/ Scheme/ DriverAttributes.php - EngineVersion.php in drivers/
lib/ Drupal/ Driver/ Database/ sqlsrv/ Scheme/ EngineVersion.php - Scheme.php in drivers/
lib/ Drupal/ Driver/ Database/ sqlsrv/ Scheme.php - UserOptions.php in drivers/
lib/ Drupal/ Driver/ Database/ sqlsrv/ Scheme/ UserOptions.php
File
- drivers/
lib/ Drupal/ Driver/ Database/ sqlsrv/ PDO/ Connection.php, line 11
Namespace
Drupal\Driver\Database\sqlsrv\PDOView source
class Connection extends PDO {
/**
* Use this when preparing a statement to
* retry operations that fail with integrity
* constraint violations. Useful when
* using MERGE statements - than can fail
* on high concurrency scenarios.
*/
const PDO_RETRYONINTEGRITYVIOLATION = 'PDO_RETRYONINTEGRITYVIOLATION';
/**
* Some environments such as Azure require retry logic
* at the statement level. Use this to enable it.
*/
const PDO_RESILIENTRETRY = 'PDO_RESILIENTRETRY';
/**
* @var Scheme
*/
private $scheme = null;
/**
* @var CacheFactoryInterface
*/
private $cache = null;
/**
* If the transaction is doomed.
*
* @var bool
*/
private $doomed_transaction = false;
/**
* The original exception that doomed the transaction.
*
* @var \Exception
*/
private $doomed_transaction_exception = null;
/**
* If we are currently inside a transaction.
*
* @var bool
*/
private $in_transaction = false;
public function __construct($dsn, $username = null, $password = null, array $driver_options = array(), CacheFactoryInterface $cache = null) {
if (empty($cache)) {
$this->cache = new CacheFactoryDefault(md5(implode(':', [
$dsn,
$username,
$password,
])));
}
else {
$this->cache = $cache;
}
parent::__construct($dsn, $username, $password, $driver_options);
$this->scheme = new Scheme($this);
}
/**
* @return Scheme
*/
public function Scheme() {
return $this->scheme;
}
/**
* @return CacheInterface
*/
public function Cache($bin = 'cache') {
return $this->cache
->get($bin);
}
/**
* If we are in a transaction and the transaction is doomed.
*/
public function InDoomedTransaction() {
return $this->doomed_transaction;
}
/**
* {@inheritdoc}
*/
public function prepare($query, $options = []) {
// Remove our custom prepare options, otherwise the PDO will
// crash.
$custom = [
self::PDO_RESILIENTRETRY,
self::PDO_RETRYONINTEGRITYVIOLATION,
];
$pdo_options = array_diff_key($options, array_flip($custom));
/** @var Statement */
$statement = parent::prepare($query, $pdo_options);
$statement
->SetConnection($this, $options);
return $statement;
}
/**
* {@inhertidoc}
*/
public function exec($statement) {
try {
return parent::exec($statement);
} catch (\PDOException $e) {
$this
->NotifyException($e);
throw $e;
}
}
/**
* {@inheritdoc}
*/
public function query($statement, $fetch_mode = null, $p1 = null, $p2 = null) {
// By overriding this we are just making sure that we are able to INTERCEPT
// any exception that might happen during a transaction.
try {
if (empty($fetch_mode)) {
return parent::query($statement);
}
switch ($fetch_mode) {
case PDO::FETCH_COLUMN:
return parent::query($statement, $fetch_mode, $p1);
case PDO::FETCH_CLASS:
return parent::query($statement, $fetch_mode, $p1, $p2);
case PDO::FETCH_INTO:
return parent::query($statement, $fetch_mode, $p1, $p2);
default:
throw new \Exception("query() call not supported. Second argument needs to be one of PDO::FETCH_COLUMN | PDO::FETCH_CLASS | PDO::FETCH_INTO. Use query_execute() instead.");
}
} catch (\PDOException $e) {
$this
->NotifyException($e);
throw $e;
}
}
protected function defaultOptions() {
return array(
'target' => 'default',
'fetch' => \PDO::FETCH_OBJ,
'throw_exception' => true,
'allow_delimiter_in_query' => false,
);
}
/**
* Custom function to easy querying data without needing to prepare.
*
* @param mixed $query
* @param array $args
* @param mixed $options
* @return mixed
* @throws PDOException
*/
public function query_execute($query, array $args = array(), $options = array()) {
try {
// Make sure we are not preparing statements.
$options[PDO::SQLSRV_ATTR_DIRECT_QUERY] = true;
/** @var Statement */
$stmt = $this
->prepare($query, $options);
$stmt
->execute($args);
return $stmt;
} catch (\PDOException $e) {
$this
->NotifyException($e);
throw $e;
}
}
/**
* {@inheritdoc}
*/
public function rollBack() {
$this->in_transaction = false;
$this->doomed_transaction = false;
return parent::rollBack();
}
/**
* {@inheritdoc}
*/
public function beginTransaction() {
parent::beginTransaction();
$this->in_transaction = true;
}
/**
* {@inheritdoc}
*/
public function commit() {
if ($this->doomed_transaction) {
// We are about to throw an Exception, this is the last word of warning...
// so it is safe to release the lock from the connection now...
$this->doomed_transaction = false;
$this
->ThrowDoomedTransactionException();
}
$this->in_transaction = false;
return parent::commit();
}
/**
* PDO Exception codes that we know will not doom
* the current transaction.
*
* @var array
*/
protected $allowed_pdo_exception_codes = array(
'42S02' => true,
);
/**
* Only to be used by statements to notify of a PDO exception.
*
* @param \PDOException $e
*/
public function NotifyException(\PDOException $e) {
// Protection against issue of PDO driver.
if ($this->in_transaction) {
// Some PDO exceptions are "safe" and will not doom the
// transaction.
if (!isset($this->allowed_pdo_exception_codes[$e
->getCode()])) {
$this->doomed_transaction = true;
$this->doomed_transaction_exception = $e;
}
}
}
/**
* @see https://github.com/Azure/msphpsql/issues/50
*
* @throws PDOException
*/
public function ThrowDoomedTransactionException() {
throw new DoomedTransactionException("Msg 3930, Level 16, State 1, Line 21\r\nThe current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.", 0, $this->doomed_transaction_exception);
}
/**
* Get the current callstack as a comment that can be appended to a query.
*
* @param string $application_root
* Application root to remove from the callstack dump.
*
* @param array $extras
* Any application specific information that needs to be dumped.
*
* @return string
*/
public function GetCallstackAsComment($application_root, array $extras = array()) {
$trim = strlen($application_root);
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// Remove last items.
$trace = array_splice($trace, 2);
$comment = PHP_EOL . PHP_EOL;
foreach ($extras as $extra) {
$comment .= $extra . PHP_EOL;
}
$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'none';
$uri = preg_replace("/[^a-zA-Z0-9]/i", "_", $uri);
$comment .= '-- url:' . $uri . PHP_EOL;
foreach ($trace as $t) {
$function = isset($t['function']) ? $t['function'] : '';
$file = '';
if (isset($t['file'])) {
$len = strlen($t['file']);
if ($len > $trim) {
$file = substr($t['file'], $trim, $len - $trim) . " [{$t['line']}]";
}
}
$comment .= '-- ' . str_pad($function, 35) . ' ' . $file . PHP_EOL;
}
$comment .= PHP_EOL;
return $comment;
}
/**
* This is a helper method to rethrow an Exception if the execution
* of a PDOStatement fails.
*
* Sometimes, as a result of a PDO Statement execution error
* the error itself will be found in the connection and no in the statement.
*
*/
public function ThrowPdoException(Statement &$statement = null, \PDOException $e = null) {
// This is what a SQL Server PDO "no error" looks like.
$null_error = array(
0 => '00000',
1 => null,
2 => null,
);
$error_info_connection = $this
->errorInfo();
if ($error_info_connection == $null_error && $e !== null) {
throw $e;
}
$error_info_statement = !empty($statement) ? $statement
->errorInfo() : $null_error;
// TODO: Concatenate error information when both connection
// and statement error info are valid.
// We rebuild a message formatted in the same way as PDO.
$error_info = $error_info_connection === $null_error ? $error_info_statement : $error_info_connection;
$code = $e && is_numeric($e
->getCode()) ? $e
->getCode() : 0;
$exception = new PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2], $code, $e);
$exception->errorInfo = $error_info;
unset($statement);
throw $exception;
}
/**
* Generate a sequence
*
* @param int $existing
* The sequence value must be greater than this value.
* @param string $name
* Name of the sequence.
* @return mixed
* @throws \Exception
*/
public function nextId($min = 0, $name = 'default') {
// To deal with crappy upstream code.
if (empty($min)) {
$min = 0;
}
if (!is_numeric($min)) {
throw new \InvalidArgumentException("Minimum id value must be an integer: {$min}");
}
// The sequence name must be unique for this installation.
$sequence_name = 'seq_' . $name;
try {
$next_id = $this
->query_execute("SELECT NEXT VALUE FOR {$sequence_name}")
->fetchField();
} catch (\Exception $e) {
// Create the sequence starting at $min + 2,
// because $min + 1 is already being used in this request.
$start = $min + 2;
// The "first id" is the same as the minimum possible
// value, because the minimum for MSSQL is inclusive, while
// this function's definition $min is exclusive (must
// be greater.
$next_id = $min + 1;
$this
->query_execute("CREATE SEQUENCE {$sequence_name} START WITH {$start} INCREMENT BY 1 MINVALUE {$next_id}");
}
// If the retrieve id is smaller or equal to de existent,
// restart the sequence to the provided number.
if ($next_id <= $min) {
$min++;
$this
->query_execute("ALTER SEQUENCE {$sequence_name} RESTART WITH {$min}");
$next_id = $this
->query_execute("SELECT NEXT VALUE FOR {$sequence_name}")
->fetchColumn();
}
return $next_id;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
Connection:: |
protected | property | PDO Exception codes that we know will not doom the current transaction. | |
Connection:: |
private | property | ||
Connection:: |
private | property | If the transaction is doomed. | |
Connection:: |
private | property | The original exception that doomed the transaction. | |
Connection:: |
private | property | If we are currently inside a transaction. | |
Connection:: |
private | property | ||
Connection:: |
public | function | ||
Connection:: |
public | function | ||
Connection:: |
public | function | ||
Connection:: |
protected | function | ||
Connection:: |
public | function | {@inhertidoc} | |
Connection:: |
public | function | Get the current callstack as a comment that can be appended to a query. | |
Connection:: |
public | function | If we are in a transaction and the transaction is doomed. | |
Connection:: |
public | function | Generate a sequence | |
Connection:: |
public | function | Only to be used by statements to notify of a PDO exception. | |
Connection:: |
constant | Some environments such as Azure require retry logic at the statement level. Use this to enable it. | ||
Connection:: |
constant | Use this when preparing a statement to retry operations that fail with integrity constraint violations. Useful when using MERGE statements - than can fail on high concurrency scenarios. | ||
Connection:: |
public | function | ||
Connection:: |
public | function | ||
Connection:: |
public | function | Custom function to easy querying data without needing to prepare. | |
Connection:: |
public | function | ||
Connection:: |
public | function | ||
Connection:: |
public | function | ||
Connection:: |
public | function | This is a helper method to rethrow an Exception if the execution of a PDOStatement fails. | |
Connection:: |
public | function |