cms/drupal/includes/database/pgsql/database.inc
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Database interface code for PostgreSQL database servers.
       
     6  */
       
     7 
       
     8 /**
       
     9  * @addtogroup database
       
    10  * @{
       
    11  */
       
    12 
       
    13 /**
       
    14  * The name by which to obtain a lock for retrieving the next insert id.
       
    15  */
       
    16 define('POSTGRESQL_NEXTID_LOCK', 1000);
       
    17 
       
    18 class DatabaseConnection_pgsql extends DatabaseConnection {
       
    19 
       
    20   public function __construct(array $connection_options = array()) {
       
    21     // This driver defaults to transaction support, except if explicitly passed FALSE.
       
    22     $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
       
    23 
       
    24     // Transactional DDL is always available in PostgreSQL,
       
    25     // but we'll only enable it if standard transactions are.
       
    26     $this->transactionalDDLSupport = $this->transactionSupport;
       
    27 
       
    28     // Default to TCP connection on port 5432.
       
    29     if (empty($connection_options['port'])) {
       
    30       $connection_options['port'] = 5432;
       
    31     }
       
    32 
       
    33     // PostgreSQL in trust mode doesn't require a password to be supplied.
       
    34     if (empty($connection_options['password'])) {
       
    35       $connection_options['password'] = NULL;
       
    36     }
       
    37     // If the password contains a backslash it is treated as an escape character
       
    38     // http://bugs.php.net/bug.php?id=53217
       
    39     // so backslashes in the password need to be doubled up.
       
    40     // The bug was reported against pdo_pgsql 1.0.2, backslashes in passwords
       
    41     // will break on this doubling up when the bug is fixed, so check the version
       
    42     //elseif (phpversion('pdo_pgsql') < 'version_this_was_fixed_in') {
       
    43     else {
       
    44       $connection_options['password'] = str_replace('\\', '\\\\', $connection_options['password']);
       
    45     }
       
    46 
       
    47     $this->connectionOptions = $connection_options;
       
    48 
       
    49     $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port'];
       
    50 
       
    51     // Allow PDO options to be overridden.
       
    52     $connection_options += array(
       
    53       'pdo' => array(),
       
    54     );
       
    55     $connection_options['pdo'] += array(
       
    56       // Prepared statements are most effective for performance when queries
       
    57       // are recycled (used several times). However, if they are not re-used,
       
    58       // prepared statements become inefficient. Since most of Drupal's
       
    59       // prepared queries are not re-used, it should be faster to emulate
       
    60       // the preparation than to actually ready statements for re-use. If in
       
    61       // doubt, reset to FALSE and measure performance.
       
    62       PDO::ATTR_EMULATE_PREPARES => TRUE,
       
    63       // Convert numeric values to strings when fetching.
       
    64       PDO::ATTR_STRINGIFY_FETCHES => TRUE,
       
    65     );
       
    66     parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
       
    67 
       
    68     // Force PostgreSQL to use the UTF-8 character set by default.
       
    69     $this->exec("SET NAMES 'UTF8'");
       
    70 
       
    71     // Execute PostgreSQL init_commands.
       
    72     if (isset($connection_options['init_commands'])) {
       
    73       $this->exec(implode('; ', $connection_options['init_commands']));
       
    74     }
       
    75   }
       
    76 
       
    77   public function prepareQuery($query) {
       
    78     // mapConditionOperator converts LIKE operations to ILIKE for consistency
       
    79     // with MySQL. However, Postgres does not support ILIKE on bytea (blobs)
       
    80     // fields.
       
    81     // To make the ILIKE operator work, we type-cast bytea fields into text.
       
    82     // @todo This workaround only affects bytea fields, but the involved field
       
    83     //   types involved in the query are unknown, so there is no way to
       
    84     //   conditionally execute this for affected queries only.
       
    85     return parent::prepareQuery(preg_replace('/ ([^ ]+) +(I*LIKE|NOT +I*LIKE) /i', ' ${1}::text ${2} ', $query));
       
    86   }
       
    87 
       
    88   public function query($query, array $args = array(), $options = array()) {
       
    89 
       
    90     $options += $this->defaultOptions();
       
    91 
       
    92     // The PDO PostgreSQL driver has a bug which
       
    93     // doesn't type cast booleans correctly when
       
    94     // parameters are bound using associative
       
    95     // arrays.
       
    96     // See http://bugs.php.net/bug.php?id=48383
       
    97     foreach ($args as &$value) {
       
    98       if (is_bool($value)) {
       
    99         $value = (int) $value;
       
   100       }
       
   101     }
       
   102 
       
   103     try {
       
   104       if ($query instanceof DatabaseStatementInterface) {
       
   105         $stmt = $query;
       
   106         $stmt->execute(NULL, $options);
       
   107       }
       
   108       else {
       
   109         $this->expandArguments($query, $args);
       
   110         $stmt = $this->prepareQuery($query);
       
   111         $stmt->execute($args, $options);
       
   112       }
       
   113 
       
   114       switch ($options['return']) {
       
   115         case Database::RETURN_STATEMENT:
       
   116           return $stmt;
       
   117         case Database::RETURN_AFFECTED:
       
   118           return $stmt->rowCount();
       
   119         case Database::RETURN_INSERT_ID:
       
   120           return $this->lastInsertId($options['sequence_name']);
       
   121         case Database::RETURN_NULL:
       
   122           return;
       
   123         default:
       
   124           throw new PDOException('Invalid return directive: ' . $options['return']);
       
   125       }
       
   126     }
       
   127     catch (PDOException $e) {
       
   128       if ($options['throw_exception']) {
       
   129         // Add additional debug information.
       
   130         if ($query instanceof DatabaseStatementInterface) {
       
   131           $e->query_string = $stmt->getQueryString();
       
   132         }
       
   133         else {
       
   134           $e->query_string = $query;
       
   135         }
       
   136         $e->args = $args;
       
   137         throw $e;
       
   138       }
       
   139       return NULL;
       
   140     }
       
   141   }
       
   142 
       
   143   public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
       
   144     return $this->query($query . ' LIMIT ' . (int) $count . ' OFFSET ' . (int) $from, $args, $options);
       
   145   }
       
   146 
       
   147   public function queryTemporary($query, array $args = array(), array $options = array()) {
       
   148     $tablename = $this->generateTemporaryTableName();
       
   149     $this->query('CREATE TEMPORARY TABLE {' . $tablename . '} AS ' . $query, $args, $options);
       
   150     return $tablename;
       
   151   }
       
   152 
       
   153   public function driver() {
       
   154     return 'pgsql';
       
   155   }
       
   156 
       
   157   public function databaseType() {
       
   158     return 'pgsql';
       
   159   }
       
   160 
       
   161   public function mapConditionOperator($operator) {
       
   162     static $specials;
       
   163 
       
   164     // Function calls not allowed in static declarations, thus this method.
       
   165     if (!isset($specials)) {
       
   166       $specials = array(
       
   167         // In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE
       
   168         // statements, we need to use ILIKE instead.
       
   169         'LIKE' => array('operator' => 'ILIKE'),
       
   170         'NOT LIKE' => array('operator' => 'NOT ILIKE'),
       
   171       );
       
   172     }
       
   173 
       
   174     return isset($specials[$operator]) ? $specials[$operator] : NULL;
       
   175   }
       
   176 
       
   177   /**
       
   178    * Retrieve the next id in a sequence.
       
   179    *
       
   180    * PostgreSQL has built in sequences. We'll use these instead of inserting
       
   181    * and updating a sequences table.
       
   182    */
       
   183   public function nextId($existing = 0) {
       
   184 
       
   185     // Retrieve the name of the sequence. This information cannot be cached
       
   186     // because the prefix may change, for example, like it does in simpletests.
       
   187     $sequence_name = $this->makeSequenceName('sequences', 'value');
       
   188 
       
   189     // When PostgreSQL gets a value too small then it will lock the table,
       
   190     // retry the INSERT and if it's still too small then alter the sequence.
       
   191     $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
       
   192     if ($id > $existing) {
       
   193       return $id;
       
   194     }
       
   195 
       
   196     // PostgreSQL advisory locks are simply locks to be used by an
       
   197     // application such as Drupal. This will prevent other Drupal processes
       
   198     // from altering the sequence while we are.
       
   199     $this->query("SELECT pg_advisory_lock(" . POSTGRESQL_NEXTID_LOCK . ")");
       
   200 
       
   201     // While waiting to obtain the lock, the sequence may have been altered
       
   202     // so lets try again to obtain an adequate value.
       
   203     $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
       
   204     if ($id > $existing) {
       
   205       $this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")");
       
   206       return $id;
       
   207     }
       
   208 
       
   209     // Reset the sequence to a higher value than the existing id.
       
   210     $this->query("ALTER SEQUENCE " . $sequence_name . " RESTART WITH " . ($existing + 1));
       
   211 
       
   212     // Retrieve the next id. We know this will be as high as we want it.
       
   213     $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
       
   214 
       
   215     $this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")");
       
   216 
       
   217     return $id;
       
   218   }
       
   219 
       
   220   public function utf8mb4IsActive() {
       
   221     return TRUE;
       
   222   }
       
   223 
       
   224   public function utf8mb4IsSupported() {
       
   225     return TRUE;
       
   226   }
       
   227 }
       
   228 
       
   229 /**
       
   230  * @} End of "addtogroup database".
       
   231  */