upgrade to OpenPublish 7.x-1.0-alpha9
[eupraxis:fsrn.git] / openpublish / includes / database / pgsql / database.inc
1 <?php
2
3 /**
4  * @file
5  * Database interface code for PostgreSQL database servers.
6  */
7
8 /**
9  * @ingroup 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 query($query, array $args = array(), $options = array()) {
78
79     $options += $this->defaultOptions();
80
81     // The PDO PostgreSQL driver has a bug which
82     // doesn't type cast booleans correctly when
83     // parameters are bound using associative
84     // arrays.
85     // See http://bugs.php.net/bug.php?id=48383
86     foreach ($args as &$value) {
87       if (is_bool($value)) {
88         $value = (int) $value;
89       }
90     }
91
92     try {
93       if ($query instanceof DatabaseStatementInterface) {
94         $stmt = $query;
95         $stmt->execute(NULL, $options);
96       }
97       else {
98         $this->expandArguments($query, $args);
99         $stmt = $this->prepareQuery($query);
100         $stmt->execute($args, $options);
101       }
102
103       switch ($options['return']) {
104         case Database::RETURN_STATEMENT:
105           return $stmt;
106         case Database::RETURN_AFFECTED:
107           return $stmt->rowCount();
108         case Database::RETURN_INSERT_ID:
109           return $this->lastInsertId($options['sequence_name']);
110         case Database::RETURN_NULL:
111           return;
112         default:
113           throw new PDOException('Invalid return directive: ' . $options['return']);
114       }
115     }
116     catch (PDOException $e) {
117       if ($options['throw_exception']) {
118         // Add additional debug information.
119         if ($query instanceof DatabaseStatementInterface) {
120           $e->query_string = $stmt->getQueryString();
121         }
122         else {
123           $e->query_string = $query;
124         }
125         $e->args = $args;
126         throw $e;
127       }
128       return NULL;
129     }
130   }
131
132   public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
133     return $this->query($query . ' LIMIT ' . (int) $count . ' OFFSET ' . (int) $from, $args, $options);
134   }
135
136   public function queryTemporary($query, array $args = array(), array $options = array()) {
137     $tablename = $this->generateTemporaryTableName();
138     $this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} AS SELECT', $query), $args, $options);
139     return $tablename;
140   }
141
142   public function driver() {
143     return 'pgsql';
144   }
145
146   public function databaseType() {
147     return 'pgsql';
148   }
149
150   public function mapConditionOperator($operator) {
151     static $specials;
152
153     // Function calls not allowed in static declarations, thus this method.
154     if (!isset($specials)) {
155       $specials = array(
156         // In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE
157         // statements, we need to use ILIKE instead.
158         'LIKE' => array('operator' => 'ILIKE'),
159         'NOT LIKE' => array('operator' => 'NOT ILIKE'),
160       );
161     }
162
163     return isset($specials[$operator]) ? $specials[$operator] : NULL;
164   }
165
166   /**
167    * Retrive a the next id in a sequence.
168    *
169    * PostgreSQL has built in sequences. We'll use these instead of inserting
170    * and updating a sequences table.
171    */
172   public function nextId($existing = 0) {
173
174     // Retrive the name of the sequence. This information cannot be cached
175     // because the prefix may change, for example, like it does in simpletests.
176     $sequence_name = $this->makeSequenceName('sequences', 'value');
177
178     // When PostgreSQL gets a value too small then it will lock the table,
179     // retry the INSERT and if it's still too small then alter the sequence.
180     $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
181     if ($id > $existing) {
182       return $id;
183     }
184
185     // PostgreSQL advisory locks are simply locks to be used by an
186     // application such as Drupal. This will prevent other Drupal proccesses
187     // from altering the sequence while we are.
188     $this->query("SELECT pg_advisory_lock(" . POSTGRESQL_NEXTID_LOCK . ")");
189
190     // While waiting to obtain the lock, the sequence may have been altered
191     // so lets try again to obtain an adequate value.
192     $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
193     if ($id > $existing) {
194       $this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")");
195       return $id;
196     }
197
198     // Reset the sequence to a higher value than the existing id.
199     $this->query("ALTER SEQUENCE " . $sequence_name . " RESTART WITH " . ($existing + 1));
200
201     // Retrive the next id. We know this will be as high as we want it.
202     $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
203
204     $this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")");
205
206     return $id;
207   }
208 }
209
210 /**
211  * @} End of "ingroup database".
212  */