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; // 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']); } $this->connectionOptions = $connection_options; $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( // Prepared statements are most effective for performance when queries // are recycled (used several times). However, if they are not re-used, // prepared statements become ineffecient. 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, ); parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); // Force PostgreSQL to use the UTF-8 character set by default. $this->exec("SET NAMES 'UTF8'"); // Execute PostgreSQL init_commands. if (isset($connection_options['init_commands'])) { $this->exec(implode('; ', $connection_options['init_commands'])); } } 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 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; } } try { if ($query instanceof DatabaseStatementInterface) { $stmt = $query; $stmt->execute(NULL, $options); } else { $this->expandArguments($query, $args); $stmt = $this->prepareQuery($query); $stmt->execute($args, $options); } switch ($options['return']) { case Database::RETURN_STATEMENT: return $stmt; case Database::RETURN_AFFECTED: return $stmt->rowCount(); case Database::RETURN_INSERT_ID: return $this->lastInsertId($options['sequence_name']); case Database::RETURN_NULL: return; default: throw new PDOException('Invalid return directive: ' . $options['return']); } } catch (PDOException $e) { if ($options['throw_exception']) { // Add additional debug information. if ($query instanceof DatabaseStatementInterface) { $e->query_string = $stmt->getQueryString(); } else { $e->query_string = $query; } $e->args = $args; throw $e; } return NULL; } } 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(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} AS SELECT', $query), $args, $options); return $tablename; } public function driver() { return 'pgsql'; } public function databaseType() { return 'pgsql'; } public function mapConditionOperator($operator) { static $specials; // Function calls not allowed in static declarations, thus this method. if (!isset($specials)) { $specials = array( // In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE // statements, we need to use ILIKE instead. 'LIKE' => array('operator' => 'ILIKE'), 'NOT LIKE' => array('operator' => 'NOT ILIKE'), ); } return isset($specials[$operator]) ? $specials[$operator] : NULL; } /** * Retrive 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) { // Retrive 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 proccesses // from altering the sequence while we are. $this->query("SELECT pg_advisory_lock(" . 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(" . 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)); // Retrive 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(" . POSTGRESQL_NEXTID_LOCK . ")"); return $id; } } /** * @} End of "addtogroup database". */