View source
<?php
namespace Drupal\Driver\Database\sqlsrv\PDO;
use Drupal\Driver\Database\sqlsrv\Component\CacheFactoryDefault;
use Drupal\Driver\Database\sqlsrv\Component\CacheFactoryInterface;
use Drupal\Driver\Database\sqlsrv\Scheme;
use PDO;
use PDOException;
class Connection extends PDO {
const PDO_RETRYONINTEGRITYVIOLATION = 'PDO_RETRYONINTEGRITYVIOLATION';
const PDO_RESILIENTRETRY = 'PDO_RESILIENTRETRY';
private $scheme = null;
private $cache = null;
private $doomed_transaction = false;
private $doomed_transaction_exception = null;
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);
}
public function Scheme() {
return $this->scheme;
}
public function Cache($bin = 'cache') {
return $this->cache
->get($bin);
}
public function InDoomedTransaction() {
return $this->doomed_transaction;
}
public function prepare($query, $options = []) {
$custom = [
self::PDO_RESILIENTRETRY,
self::PDO_RETRYONINTEGRITYVIOLATION,
];
$pdo_options = array_diff_key($options, array_flip($custom));
$statement = parent::prepare($query, $pdo_options);
$statement
->SetConnection($this, $options);
return $statement;
}
public function exec($statement) {
try {
return parent::exec($statement);
} catch (\PDOException $e) {
$this
->NotifyException($e);
throw $e;
}
}
public function query($statement, $fetch_mode = null, $p1 = null, $p2 = null) {
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,
);
}
public function query_execute($query, array $args = array(), $options = array()) {
try {
$options[PDO::SQLSRV_ATTR_DIRECT_QUERY] = true;
$stmt = $this
->prepare($query, $options);
$stmt
->execute($args);
return $stmt;
} catch (\PDOException $e) {
$this
->NotifyException($e);
throw $e;
}
}
public function rollBack() {
$this->in_transaction = false;
$this->doomed_transaction = false;
return parent::rollBack();
}
public function beginTransaction() {
parent::beginTransaction();
$this->in_transaction = true;
}
public function commit() {
if ($this->doomed_transaction) {
$this->doomed_transaction = false;
$this
->ThrowDoomedTransactionException();
}
$this->in_transaction = false;
return parent::commit();
}
protected $allowed_pdo_exception_codes = array(
'42S02' => true,
);
public function NotifyException(\PDOException $e) {
if ($this->in_transaction) {
if (!isset($this->allowed_pdo_exception_codes[$e
->getCode()])) {
$this->doomed_transaction = true;
$this->doomed_transaction_exception = $e;
}
}
}
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);
}
public function GetCallstackAsComment($application_root, array $extras = array()) {
$trim = strlen($application_root);
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$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;
}
public function ThrowPdoException(Statement &$statement = null, \PDOException $e = null) {
$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;
$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;
}
public function nextId($min = 0, $name = 'default') {
if (empty($min)) {
$min = 0;
}
if (!is_numeric($min)) {
throw new \InvalidArgumentException("Minimum id value must be an integer: {$min}");
}
$sequence_name = 'seq_' . $name;
try {
$next_id = $this
->query_execute("SELECT NEXT VALUE FOR {$sequence_name}")
->fetchField();
} catch (\Exception $e) {
$start = $min + 2;
$next_id = $min + 1;
$this
->query_execute("CREATE SEQUENCE {$sequence_name} START WITH {$start} INCREMENT BY 1 MINVALUE {$next_id}");
}
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;
}
}