1 |
85ad3d82
|
Assos Assos
|
<?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 |
4444412d
|
Julien Enselme
|
$this->query('CREATE TEMPORARY TABLE {' . $tablename . '} AS ' . $query, $args, $options);
|
150 |
85ad3d82
|
Assos Assos
|
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 |
|
|
|
221 |
|
|
/**
|
222 |
|
|
* @} End of "addtogroup database".
|
223 |
|
|
*/ |