database.inc

Same filename in this branch
  1. 7.x includes/database/sqlite/database.inc
  2. 7.x includes/database/mysql/database.inc
  3. 7.x includes/database/database.inc
Same filename and directory in other branches
  1. 8.9.x core/includes/database.inc

Database interface code for PostgreSQL database servers.

File

includes/database/pgsql/database.inc

View source
<?php


/**
 * @file
 * Database interface code for PostgreSQL database servers.
 */

/**
 * @addtogroup database
 * @{
 */

/**
 * The name by which to obtain a lock for retrieving the next insert id.
 */
define('POSTGRESQL_NEXTID_LOCK', 1000);
class DatabaseConnection_pgsql extends DatabaseConnection {
    public function __construct(array $connection_options = array()) {
        // 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;
        // 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;
        }
        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 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,
        );
        parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
        // 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']));
        }
    }
    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:
                    $sequence_name = isset($options['sequence_name']) ? $options['sequence_name'] : NULL;
                    return $this->connection
                        ->lastInsertId($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->errorInfo['query_string'] = $stmt->getQueryString();
                }
                else {
                    $e->errorInfo['query_string'] = $query;
                }
                $e->errorInfo['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('CREATE TEMPORARY TABLE {' . $tablename . '} AS ' . $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;
    }
    
    /**
     * Retrieve 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(" . 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));
        // 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(" . POSTGRESQL_NEXTID_LOCK . ")");
        return $id;
    }
    public function utf8mb4IsActive() {
        return TRUE;
    }
    public function utf8mb4IsSupported() {
        return TRUE;
    }

}

/**
 * @} End of "addtogroup database".
 */

Constants

Title Deprecated Summary
POSTGRESQL_NEXTID_LOCK The name by which to obtain a lock for retrieving the next insert id.

Classes

Title Deprecated Summary
DatabaseConnection_pgsql

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.