Project

General

Profile

Paste
Download (8.02 KB) Statistics
| Branch: | Revision:

root / drupal7 / includes / database / pgsql / database.inc @ b0dc3a2e

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 retrive 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 ineffecient. 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
   * Retrive a 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
    // Retrive 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 proccesses
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
    // Retrive 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
 */