Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 19 |
CRAP | |
18.18% |
20 / 110 |
| Connection | |
0.00% |
0 / 1 |
|
0.00% |
0 / 19 |
1364.05 | |
18.18% |
20 / 110 |
| __construct | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 8 |
|||
| open | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 14 |
|||
| query | |
0.00% |
0 / 1 |
110 | |
0.00% |
0 / 19 |
|||
| prepareQuery | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| queryRange | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| queryTemporary | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| escapeField | |
0.00% |
0 / 1 |
4.01 | |
90.91% |
10 / 11 |
|||
| escapeAlias | |
0.00% |
0 / 1 |
3.04 | |
83.33% |
5 / 6 |
|||
| escapeTable | |
0.00% |
0 / 1 |
3.04 | |
83.33% |
5 / 6 |
|||
| driver | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| databaseType | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| createDatabase | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 8 |
|||
| mapConditionOperator | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 2 |
|||
| nextId | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 13 |
|||
| getFullQualifiedTableName | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| addSavepoint | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
| releaseSavepoint | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
| rollbackSavepoint | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
| upsert | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\Core\Database\Driver\pgsql\Connection. | |
| */ | |
| namespace Drupal\Core\Database\Driver\pgsql; | |
| use Drupal\Core\Database\Database; | |
| use Drupal\Core\Database\Connection as DatabaseConnection; | |
| use Drupal\Core\Database\DatabaseNotFoundException; | |
| /** | |
| * @addtogroup database | |
| * @{ | |
| */ | |
| /** | |
| * PostgreSQL implementation of \Drupal\Core\Database\Connection. | |
| */ | |
| class Connection extends DatabaseConnection { | |
| /** | |
| * The name by which to obtain a lock for retrieve the next insert id. | |
| */ | |
| const POSTGRESQL_NEXTID_LOCK = 1000; | |
| /** | |
| * Error code for "Unknown database" error. | |
| */ | |
| const DATABASE_NOT_FOUND = 7; | |
| /** | |
| * The list of PostgreSQL reserved key words. | |
| * | |
| * @see http://www.postgresql.org/docs/9.4/static/sql-keywords-appendix.html | |
| */ | |
| protected $postgresqlReservedKeyWords = ['all', 'analyse', 'analyze', 'and', | |
| 'any', 'array', 'as', 'asc', 'asymmetric', 'authorization', 'binary', 'both', | |
| 'case', 'cast', 'check', 'collate', 'collation', 'column', 'concurrently', | |
| 'constraint', 'create', 'cross', 'current_catalog', 'current_date', | |
| 'current_role', 'current_schema', 'current_time', 'current_timestamp', | |
| 'current_user', 'default', 'deferrable', 'desc', 'distinct', 'do', 'else', | |
| 'end', 'except', 'false', 'fetch', 'for', 'foreign', 'freeze', 'from', 'full', | |
| 'grant', 'group', 'having', 'ilike', 'in', 'initially', 'inner', 'intersect', | |
| 'into', 'is', 'isnull', 'join', 'lateral', 'leading', 'left', 'like', 'limit', | |
| 'localtime', 'localtimestamp', 'natural', 'not', 'notnull', 'null', 'offset', | |
| 'on', 'only', 'or', 'order', 'outer', 'over', 'overlaps', 'placing', | |
| 'primary', 'references', 'returning', 'right', 'select', 'session_user', | |
| 'similar', 'some', 'symmetric', 'table', 'then', 'to', 'trailing', 'true', | |
| 'union', 'unique', 'user', 'using', 'variadic', 'verbose', 'when', 'where', | |
| 'window', 'with']; | |
| /** | |
| * Constructs a connection object. | |
| */ | |
| public function __construct(\PDO $connection, array $connection_options) { | |
| parent::__construct($connection, $connection_options); | |
| // This driver defaults to transaction support, except if explicitly passed FALSE. | |
| $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE); | |
| // Transactional DDL is always available in PostgreSQL, | |
| // but we'll only enable it if standard transactions are. | |
| $this->transactionalDDLSupport = $this->transactionSupport; | |
| $this->connectionOptions = $connection_options; | |
| // Force PostgreSQL to use the UTF-8 character set by default. | |
| $this->connection->exec("SET NAMES 'UTF8'"); | |
| // Execute PostgreSQL init_commands. | |
| if (isset($connection_options['init_commands'])) { | |
| $this->connection->exec(implode('; ', $connection_options['init_commands'])); | |
| } | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public static function open(array &$connection_options = array()) { | |
| // Default to TCP connection on port 5432. | |
| if (empty($connection_options['port'])) { | |
| $connection_options['port'] = 5432; | |
| } | |
| // PostgreSQL in trust mode doesn't require a password to be supplied. | |
| if (empty($connection_options['password'])) { | |
| $connection_options['password'] = NULL; | |
| } | |
| // If the password contains a backslash it is treated as an escape character | |
| // http://bugs.php.net/bug.php?id=53217 | |
| // so backslashes in the password need to be doubled up. | |
| // The bug was reported against pdo_pgsql 1.0.2, backslashes in passwords | |
| // will break on this doubling up when the bug is fixed, so check the version | |
| //elseif (phpversion('pdo_pgsql') < 'version_this_was_fixed_in') { | |
| else { | |
| $connection_options['password'] = str_replace('\\', '\\\\', $connection_options['password']); | |
| } | |
| $connection_options['database'] = (!empty($connection_options['database']) ? $connection_options['database'] : 'template1'); | |
| $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port']; | |
| // Allow PDO options to be overridden. | |
| $connection_options += array( | |
| 'pdo' => array(), | |
| ); | |
| $connection_options['pdo'] += array( | |
| \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, | |
| // Prepared statements are most effective for performance when queries | |
| // are recycled (used several times). However, if they are not re-used, | |
| // prepared statements become inefficient. Since most of Drupal's | |
| // prepared queries are not re-used, it should be faster to emulate | |
| // the preparation than to actually ready statements for re-use. If in | |
| // doubt, reset to FALSE and measure performance. | |
| \PDO::ATTR_EMULATE_PREPARES => TRUE, | |
| // Convert numeric values to strings when fetching. | |
| \PDO::ATTR_STRINGIFY_FETCHES => TRUE, | |
| ); | |
| $pdo = new \PDO($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); | |
| return $pdo; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function query($query, array $args = array(), $options = array()) { | |
| $options += $this->defaultOptions(); | |
| // The PDO PostgreSQL driver has a bug which doesn't type cast booleans | |
| // correctly when parameters are bound using associative arrays. | |
| // @see http://bugs.php.net/bug.php?id=48383 | |
| foreach ($args as &$value) { | |
| if (is_bool($value)) { | |
| $value = (int) $value; | |
| } | |
| } | |
| // We need to wrap queries with a savepoint if: | |
| // - Currently in a transaction. | |
| // - A 'mimic_implicit_commit' does not exist already. | |
| // - The query is not a savepoint query. | |
| $wrap_with_savepoint = $this->inTransaction() && | |
| !isset($this->transactionLayers['mimic_implicit_commit']) && | |
| !(is_string($query) && ( | |
| stripos($query, 'ROLLBACK TO SAVEPOINT ') === 0 || | |
| stripos($query, 'RELEASE SAVEPOINT ') === 0 || | |
| stripos($query, 'SAVEPOINT ') === 0 | |
| ) | |
| ); | |
| if ($wrap_with_savepoint) { | |
| // Create a savepoint so we can rollback a failed query. This is so we can | |
| // mimic MySQL and SQLite transactions which don't fail if a single query | |
| // fails. This is important for tables that are created on demand. For | |
| // example, \Drupal\Core\Cache\DatabaseBackend. | |
| $this->addSavepoint(); | |
| try { | |
| $return = parent::query($query, $args, $options); | |
| $this->releaseSavepoint(); | |
| } | |
| catch (\Exception $e) { | |
| $this->rollbackSavepoint(); | |
| throw $e; | |
| } | |
| } | |
| else { | |
| $return = parent::query($query, $args, $options); | |
| } | |
| return $return; | |
| } | |
| public function prepareQuery($query) { | |
| // mapConditionOperator converts LIKE operations to ILIKE for consistency | |
| // with MySQL. However, Postgres does not support ILIKE on bytea (blobs) | |
| // fields. | |
| // To make the ILIKE operator work, we type-cast bytea fields into text. | |
| // @todo This workaround only affects bytea fields, but the involved field | |
| // types involved in the query are unknown, so there is no way to | |
| // conditionally execute this for affected queries only. | |
| return parent::prepareQuery(preg_replace('/ ([^ ]+) +(I*LIKE|NOT +I*LIKE) /i', ' ${1}::text ${2} ', $query)); | |
| } | |
| public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { | |
| return $this->query($query . ' LIMIT ' . (int) $count . ' OFFSET ' . (int) $from, $args, $options); | |
| } | |
| public function queryTemporary($query, array $args = array(), array $options = array()) { | |
| $tablename = $this->generateTemporaryTableName(); | |
| $this->query('CREATE TEMPORARY TABLE {' . $tablename . '} AS ' . $query, $args, $options); | |
| return $tablename; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function escapeField($field) { | |
| $escaped = parent::escapeField($field); | |
| // Remove any invalid start character. | |
| $escaped = preg_replace('/^[^A-Za-z0-9_]/', '', $escaped); | |
| // The pgsql database driver does not support field names that contain | |
| // periods (supported by PostgreSQL server) because this method may be | |
| // called by a field with a table alias as part of SQL conditions or | |
| // order by statements. This will consider a period as a table alias | |
| // identifier, and split the string at the first period. | |
| if (preg_match('/^([A-Za-z0-9_]+)"?[.]"?([A-Za-z0-9_.]+)/', $escaped, $parts)) { | |
| $table = $parts[1]; | |
| $column = $parts[2]; | |
| // Use escape alias because escapeField may contain multiple periods that | |
| // need to be escaped. | |
| $escaped = $this->escapeTable($table) . '.' . $this->escapeAlias($column); | |
| } | |
| elseif (preg_match('/[A-Z]/', $escaped)) { | |
| // Quote the field name for case-sensitivity. | |
| $escaped = '"' . $escaped . '"'; | |
| } | |
| elseif (in_array(strtolower($escaped), $this->postgresqlReservedKeyWords)) { | |
| // Quote the field name for PostgreSQL reserved key words. | |
| $escaped = '"' . $escaped . '"'; | |
| } | |
| return $escaped; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function escapeAlias($field) { | |
| $escaped = preg_replace('/[^A-Za-z0-9_]+/', '', $field); | |
| // Escape the alias in quotes for case-sensitivity. | |
| if (preg_match('/[A-Z]/', $escaped)) { | |
| $escaped = '"' . $escaped . '"'; | |
| } | |
| elseif (in_array(strtolower($escaped), $this->postgresqlReservedKeyWords)) { | |
| // Quote the alias name for PostgreSQL reserved key words. | |
| $escaped = '"' . $escaped . '"'; | |
| } | |
| return $escaped; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function escapeTable($table) { | |
| $escaped = parent::escapeTable($table); | |
| // Quote identifier to make it case-sensitive. | |
| if (preg_match('/[A-Z]/', $escaped)) { | |
| $escaped = '"' . $escaped . '"'; | |
| } | |
| elseif (in_array(strtolower($escaped), $this->postgresqlReservedKeyWords)) { | |
| // Quote the table name for PostgreSQL reserved key words. | |
| $escaped = '"' . $escaped . '"'; | |
| } | |
| return $escaped; | |
| } | |
| public function driver() { | |
| return 'pgsql'; | |
| } | |
| public function databaseType() { | |
| return 'pgsql'; | |
| } | |
| /** | |
| * Overrides \Drupal\Core\Database\Connection::createDatabase(). | |
| * | |
| * @param string $database | |
| * The name of the database to create. | |
| * | |
| * @throws \Drupal\Core\Database\DatabaseNotFoundException | |
| */ | |
| public function createDatabase($database) { | |
| // Escape the database name. | |
| $database = Database::getConnection()->escapeDatabase($database); | |
| // If the PECL intl extension is installed, use it to determine the proper | |
| // locale. Otherwise, fall back to en_US. | |
| if (class_exists('Locale')) { | |
| $locale = \Locale::getDefault(); | |
| } | |
| else { | |
| $locale = 'en_US'; | |
| } | |
| try { | |
| // Create the database and set it as active. | |
| $this->connection->exec("CREATE DATABASE $database WITH TEMPLATE template0 ENCODING='utf8' LC_CTYPE='$locale.utf8' LC_COLLATE='$locale.utf8'"); | |
| } | |
| catch (\Exception $e) { | |
| throw new DatabaseNotFoundException($e->getMessage()); | |
| } | |
| } | |
| public function mapConditionOperator($operator) { | |
| static $specials = array( | |
| // In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE | |
| // statements, we need to use ILIKE instead. | |
| 'LIKE' => array('operator' => 'ILIKE'), | |
| 'LIKE BINARY' => array('operator' => 'LIKE'), | |
| 'NOT LIKE' => array('operator' => 'NOT ILIKE'), | |
| 'REGEXP' => array('operator' => '~*'), | |
| ); | |
| return isset($specials[$operator]) ? $specials[$operator] : NULL; | |
| } | |
| /** | |
| * Retrieve a the next id in a sequence. | |
| * | |
| * PostgreSQL has built in sequences. We'll use these instead of inserting | |
| * and updating a sequences table. | |
| */ | |
| public function nextId($existing = 0) { | |
| // Retrieve the name of the sequence. This information cannot be cached | |
| // because the prefix may change, for example, like it does in simpletests. | |
| $sequence_name = $this->makeSequenceName('sequences', 'value'); | |
| // When PostgreSQL gets a value too small then it will lock the table, | |
| // retry the INSERT and if it's still too small then alter the sequence. | |
| $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField(); | |
| if ($id > $existing) { | |
| return $id; | |
| } | |
| // PostgreSQL advisory locks are simply locks to be used by an | |
| // application such as Drupal. This will prevent other Drupal processes | |
| // from altering the sequence while we are. | |
| $this->query("SELECT pg_advisory_lock(" . self::POSTGRESQL_NEXTID_LOCK . ")"); | |
| // While waiting to obtain the lock, the sequence may have been altered | |
| // so lets try again to obtain an adequate value. | |
| $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField(); | |
| if ($id > $existing) { | |
| $this->query("SELECT pg_advisory_unlock(" . self::POSTGRESQL_NEXTID_LOCK . ")"); | |
| return $id; | |
| } | |
| // Reset the sequence to a higher value than the existing id. | |
| $this->query("ALTER SEQUENCE " . $sequence_name . " RESTART WITH " . ($existing + 1)); | |
| // Retrieve the next id. We know this will be as high as we want it. | |
| $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField(); | |
| $this->query("SELECT pg_advisory_unlock(" . self::POSTGRESQL_NEXTID_LOCK . ")"); | |
| return $id; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getFullQualifiedTableName($table) { | |
| $options = $this->getConnectionOptions(); | |
| $prefix = $this->tablePrefix($table); | |
| // The fully qualified table name in PostgreSQL is in the form of | |
| // <database>.<schema>.<table>, so we have to include the 'public' schema in | |
| // the return value. | |
| return $options['database'] . '.public.' . $prefix . $table; | |
| } | |
| /** | |
| * Add a new savepoint with an unique name. | |
| * | |
| * The main use for this method is to mimic InnoDB functionality, which | |
| * provides an inherent savepoint before any query in a transaction. | |
| * | |
| * @param $savepoint_name | |
| * A string representing the savepoint name. By default, | |
| * "mimic_implicit_commit" is used. | |
| * | |
| * @see Drupal\Core\Database\Connection::pushTransaction(). | |
| */ | |
| public function addSavepoint($savepoint_name = 'mimic_implicit_commit') { | |
| if ($this->inTransaction()) { | |
| $this->pushTransaction($savepoint_name); | |
| } | |
| } | |
| /** | |
| * Release a savepoint by name. | |
| * | |
| * @param $savepoint_name | |
| * A string representing the savepoint name. By default, | |
| * "mimic_implicit_commit" is used. | |
| * | |
| * @see Drupal\Core\Database\Connection::popTransaction(). | |
| */ | |
| public function releaseSavepoint($savepoint_name = 'mimic_implicit_commit') { | |
| if (isset($this->transactionLayers[$savepoint_name])) { | |
| $this->popTransaction($savepoint_name); | |
| } | |
| } | |
| /** | |
| * Rollback a savepoint by name if it exists. | |
| * | |
| * @param $savepoint_name | |
| * A string representing the savepoint name. By default, | |
| * "mimic_implicit_commit" is used. | |
| */ | |
| public function rollbackSavepoint($savepoint_name = 'mimic_implicit_commit') { | |
| if (isset($this->transactionLayers[$savepoint_name])) { | |
| $this->rollback($savepoint_name); | |
| } | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function upsert($table, array $options = array()) { | |
| // Use the (faster) native Upsert implementation for PostgreSQL >= 9.5. | |
| if (version_compare($this->version(), '9.5', '>=')) { | |
| $class = $this->getDriverClass('NativeUpsert'); | |
| } | |
| else { | |
| $class = $this->getDriverClass('Upsert'); | |
| } | |
| return new $class($this, $table, $options); | |
| } | |
| } | |
| /** | |
| * @} End of "addtogroup database". | |
| */ |