1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file has all the main functions in it that relate to the Postgre database. |
5
|
|
|
* |
6
|
|
|
* @name ElkArte Forum |
7
|
|
|
* @copyright ElkArte Forum contributors |
8
|
|
|
* @license BSD http://opensource.org/licenses/BSD-3-Clause |
9
|
|
|
* |
10
|
|
|
* This file contains code covered by: |
11
|
|
|
* copyright: 2011 Simple Machines (http://www.simplemachines.org) |
12
|
|
|
* license: BSD, See included LICENSE.TXT for terms and conditions. |
13
|
|
|
* |
14
|
|
|
* @version 1.1 |
15
|
|
|
* |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
// Let's define the name of the class so that we will be able to use it in the instantiations |
19
|
|
|
if (!defined('DB_TYPE')) |
20
|
|
|
define('DB_TYPE', 'PostgreSQL'); |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* PostgreSQL database class, implements database class to control mysql functions |
24
|
|
|
*/ |
25
|
|
|
class Database_PostgreSQL extends Database_Abstract |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* Holds current instance of the class |
29
|
|
|
* @var Database_PostgreSQL |
30
|
|
|
*/ |
31
|
|
|
private static $_db = null; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Holds last query result |
35
|
|
|
* @var string |
36
|
|
|
*/ |
37
|
|
|
private $_db_last_result = null; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Since PostgreSQL doesn't support INSERT REPLACE we are using this to remember |
41
|
|
|
* the rows affected by the delete |
42
|
|
|
* @var int |
43
|
|
|
*/ |
44
|
|
|
private $_db_replace_result = null; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* A variable to remember if a transaction was started already or not |
48
|
|
|
* @var boolean |
49
|
|
|
*/ |
50
|
|
|
private $_in_transaction = false; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Initializes a database connection. |
54
|
|
|
* It returns the connection, if successful. |
55
|
|
|
* |
56
|
|
|
* @param string $db_server |
57
|
|
|
* @param string $db_name |
58
|
|
|
* @param string $db_user |
59
|
|
|
* @param string $db_passwd |
60
|
|
|
* @param string $db_prefix |
61
|
|
|
* @param mixed[] $db_options |
62
|
|
|
* |
63
|
|
|
* @return resource |
64
|
|
|
* @throws Elk_Exception |
65
|
|
|
*/ |
66
|
|
|
public static function initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $db_options = array()) |
|
|
|
|
67
|
|
|
{ |
68
|
|
|
// initialize the instance... if not done already! |
69
|
|
|
if (self::$_db === null) |
70
|
|
|
self::$_db = new self(); |
71
|
|
|
|
72
|
|
View Code Duplication |
if (!empty($db_options['port'])) |
73
|
|
|
$db_port = ' port=' . (int) $db_options['port']; |
74
|
|
|
else |
75
|
|
|
$db_port = ''; |
76
|
|
|
|
77
|
|
|
if (!empty($db_options['persist'])) |
78
|
|
|
$connection = @pg_pconnect('host=' . $db_server . $db_port . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\''); |
79
|
|
|
else |
80
|
|
|
$connection = @pg_connect('host=' . $db_server . $db_port . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\''); |
81
|
|
|
|
82
|
|
|
// Something's wrong, show an error if its fatal (which we assume it is) |
83
|
|
View Code Duplication |
if (!$connection) |
84
|
|
|
{ |
85
|
|
|
if (!empty($db_options['non_fatal'])) |
86
|
|
|
return null; |
87
|
|
|
else |
88
|
|
|
Errors::instance()->display_db_error(); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
self::$_db->_connection = $connection; |
92
|
|
|
|
93
|
|
|
return $connection; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Fix the database prefix if necessary. |
98
|
|
|
* Do nothing on postgreSQL |
99
|
|
|
* |
100
|
|
|
* @param string $db_prefix |
101
|
|
|
* @param string $db_name |
102
|
|
|
* |
103
|
|
|
* @return string |
104
|
|
|
*/ |
105
|
|
|
public function fix_prefix($db_prefix, $db_name) |
106
|
|
|
{ |
107
|
|
|
return $db_prefix; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Do a query. Takes care of errors too. |
112
|
|
|
* Special queries may need additional replacements to be appropriate |
113
|
|
|
* for PostgreSQL. |
114
|
|
|
* |
115
|
|
|
* @param string $identifier |
116
|
|
|
* @param string $db_string |
117
|
|
|
* @param mixed[] $db_values |
118
|
|
|
* @param resource|null $connection |
119
|
|
|
* |
120
|
|
|
* @return bool|resource|string |
121
|
|
|
* @throws Elk_Exception |
122
|
|
|
*/ |
123
|
|
|
public function query($identifier, $db_string, $db_values = array(), $connection = null) |
124
|
|
|
{ |
125
|
|
|
global $db_show_debug, $time_start, $modSettings; |
126
|
|
|
|
127
|
|
|
// Decide which connection to use. |
128
|
|
|
$connection = $connection === null ? $this->_connection : $connection; |
129
|
|
|
|
130
|
|
|
// Special queries that need processing. |
131
|
|
|
$replacements = array( |
132
|
|
|
'alter_table' => array( |
133
|
|
|
'~(.+)~' => '', |
134
|
|
|
), |
135
|
|
|
'ban_suggest_error_ips' => array( |
136
|
|
|
'~RLIKE~' => '~', |
137
|
|
|
'~\\.~' => '\.', |
138
|
|
|
), |
139
|
|
|
'ban_suggest_message_ips' => array( |
140
|
|
|
'~RLIKE~' => '~', |
141
|
|
|
'~\\.~' => '\.', |
142
|
|
|
), |
143
|
|
|
'consolidate_spider_stats' => array( |
144
|
|
|
'~MONTH\(log_time\), DAYOFMONTH\(log_time\)~' => 'MONTH(CAST(CAST(log_time AS abstime) AS timestamp)), DAYOFMONTH(CAST(CAST(log_time AS abstime) AS timestamp))', |
145
|
|
|
), |
146
|
|
|
'display_get_post_poster' => array( |
147
|
|
|
'~GROUP BY id_msg\s+HAVING~' => 'AND', |
148
|
|
|
), |
149
|
|
|
'attach_download_increase' => array( |
150
|
|
|
'~LOW_PRIORITY~' => '', |
151
|
|
|
), |
152
|
|
|
'boardindex_fetch_boards' => array( |
153
|
|
|
'~COALESCE\(lb.id_msg, 0\) >= b.id_msg_updated~' => 'CASE WHEN COALESCE(lb.id_msg, 0) >= b.id_msg_updated THEN 1 ELSE 0 END', |
154
|
|
|
), |
155
|
|
|
'get_random_number' => array( |
156
|
|
|
'~RAND~' => 'RANDOM', |
157
|
|
|
), |
158
|
|
|
'insert_log_search_topics' => array( |
159
|
|
|
'~NOT RLIKE~' => '!~', |
160
|
|
|
), |
161
|
|
|
'insert_log_search_results_no_index' => array( |
162
|
|
|
'~NOT RLIKE~' => '!~', |
163
|
|
|
), |
164
|
|
|
'insert_log_search_results_subject' => array( |
165
|
|
|
'~NOT RLIKE~' => '!~', |
166
|
|
|
), |
167
|
|
|
'pm_conversation_list' => array( |
168
|
|
|
'~ORDER\\s+BY\\s+\\{raw:sort\\}~' => 'ORDER BY ' . (isset($db_values['sort']) ? ($db_values['sort'] === 'pm.id_pm' ? 'MAX(pm.id_pm)' : $db_values['sort']) : ''), |
169
|
|
|
), |
170
|
|
|
'top_topic_starters' => array( |
171
|
|
|
'~ORDER BY FIND_IN_SET\(id_member,(.+?)\)~' => 'ORDER BY STRPOS(\',\' || $1 || \',\', \',\' || id_member|| \',\')', |
172
|
|
|
), |
173
|
|
|
'unread_replies' => array( |
174
|
|
|
'~SELECT\\s+DISTINCT\\s+t.id_topic~' => 'SELECT t.id_topic, {raw:sort}', |
175
|
|
|
), |
176
|
|
|
'profile_board_stats' => array( |
177
|
|
|
'~COUNT\(\*\) \/ MAX\(b.num_posts\)~' => 'CAST(COUNT(*) AS DECIMAL) / CAST(b.num_posts AS DECIMAL)', |
178
|
|
|
), |
179
|
|
|
); |
180
|
|
|
|
181
|
|
|
if (isset($replacements[$identifier])) |
182
|
|
|
$db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string); |
183
|
|
|
|
184
|
|
|
// Limits need to be a little different. |
185
|
|
|
$db_string = preg_replace('~\sLIMIT\s(\d+|{int:.+}),\s*(\d+|{int:.+})\s*$~i', 'LIMIT $2 OFFSET $1', $db_string); |
186
|
|
|
|
187
|
|
|
if (trim($db_string) == '') |
188
|
|
|
return false; |
189
|
|
|
|
190
|
|
|
// Comments that are allowed in a query are preg_removed. |
191
|
|
|
static $allowed_comments_from = array( |
192
|
|
|
'~\s+~s', |
193
|
|
|
'~/\*!40001 SQL_NO_CACHE \*/~', |
194
|
|
|
'~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~', |
195
|
|
|
'~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~', |
196
|
|
|
); |
197
|
|
|
static $allowed_comments_to = array( |
198
|
|
|
' ', |
199
|
|
|
'', |
200
|
|
|
'', |
201
|
|
|
'', |
202
|
|
|
); |
203
|
|
|
|
204
|
|
|
// One more query.... |
205
|
|
|
$this->_query_count++; |
206
|
|
|
$this->_db_replace_result = null; |
207
|
|
|
|
208
|
|
View Code Duplication |
if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override'])) |
209
|
|
|
$this->error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__); |
210
|
|
|
|
211
|
|
View Code Duplication |
if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false)) |
212
|
|
|
{ |
213
|
|
|
// Store these values for use in the callback function. |
214
|
|
|
$this->_db_callback_values = $db_values; |
215
|
|
|
$this->_db_callback_connection = $connection; |
216
|
|
|
|
217
|
|
|
// Inject the values passed to this function. |
218
|
|
|
$db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', array($this, 'replacement__callback'), $db_string); |
219
|
|
|
|
220
|
|
|
// No need for them any longer. |
221
|
|
|
$this->_db_callback_values = array(); |
222
|
|
|
$this->_db_callback_connection = null; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
// Debugging. |
226
|
|
View Code Duplication |
if ($db_show_debug === true) |
227
|
|
|
{ |
228
|
|
|
$debug = Debug::instance(); |
229
|
|
|
|
230
|
|
|
// Get the file and line number this function was called. |
231
|
|
|
list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__); |
232
|
|
|
|
233
|
|
|
if (!empty($_SESSION['debug_redirect'])) |
234
|
|
|
{ |
235
|
|
|
$debug->merge_db($_SESSION['debug_redirect']); |
236
|
|
|
// @todo this may be off by 1 |
237
|
|
|
$this->_query_count += count($_SESSION['debug_redirect']); |
238
|
|
|
$_SESSION['debug_redirect'] = array(); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
// Don't overload it. |
242
|
|
|
$st = microtime(true); |
243
|
|
|
$db_cache = array(); |
244
|
|
|
$db_cache['q'] = $this->_query_count < 50 ? $db_string : '...'; |
245
|
|
|
$db_cache['f'] = $file; |
246
|
|
|
$db_cache['l'] = $line; |
247
|
|
|
$db_cache['s'] = $st - $time_start; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
// First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over. |
251
|
|
|
if (empty($modSettings['disableQueryCheck'])) |
252
|
|
|
{ |
253
|
|
|
$clean = ''; |
254
|
|
|
$old_pos = 0; |
255
|
|
|
$pos = -1; |
256
|
|
View Code Duplication |
while (true) |
257
|
|
|
{ |
258
|
|
|
$pos = strpos($db_string, '\'', $pos + 1); |
259
|
|
|
if ($pos === false) |
260
|
|
|
break; |
261
|
|
|
$clean .= substr($db_string, $old_pos, $pos - $old_pos); |
262
|
|
|
|
263
|
|
|
while (true) |
264
|
|
|
{ |
265
|
|
|
$pos1 = strpos($db_string, '\'', $pos + 1); |
266
|
|
|
$pos2 = strpos($db_string, '\'\'', $pos + 1); |
267
|
|
|
|
268
|
|
|
if ($pos1 === false) |
269
|
|
|
break; |
270
|
|
|
elseif ($pos2 === false || $pos2 > $pos1) |
271
|
|
|
{ |
272
|
|
|
$pos = $pos1; |
273
|
|
|
break; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
$pos = $pos2 + 1; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
$clean .= ' %s '; |
280
|
|
|
$old_pos = $pos + 1; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
$clean .= substr($db_string, $old_pos); |
284
|
|
|
$clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean))); |
285
|
|
|
|
286
|
|
|
// Comments? We don't use comments in our queries, we leave 'em outside! |
287
|
|
View Code Duplication |
if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false) |
288
|
|
|
$fail = true; |
289
|
|
|
// Trying to change passwords, slow us down, or something? |
290
|
|
|
elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0) |
291
|
|
|
$fail = true; |
292
|
|
|
elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0) |
293
|
|
|
$fail = true; |
294
|
|
|
|
295
|
|
|
if (!empty($fail) && class_exists('Errors')) |
296
|
|
|
$this->error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__); |
297
|
|
|
|
298
|
|
|
// If we are updating something, better start a transaction so that indexes may be kept consistent |
299
|
|
|
if (!$this->_in_transaction && strpos($clean, 'update') !== false) |
300
|
|
|
$this->db_transaction('begin', $connection); |
|
|
|
|
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
$this->_db_last_result = @pg_query($connection, $db_string); |
|
|
|
|
304
|
|
|
|
305
|
|
|
// @deprecated since 1.1 - use skip_next_error method |
306
|
|
|
if (!empty($db_values['db_error_skip'])) |
307
|
|
|
{ |
308
|
|
|
$this->_skip_error = true; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
if ($this->_db_last_result === false && !$this->_skip_error) |
312
|
|
|
{ |
313
|
|
|
$this->error($db_string, $connection); |
|
|
|
|
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
// Revert not to skip errors |
317
|
|
|
if ($this->_skip_error === true) |
318
|
|
|
{ |
319
|
|
|
$this->_skip_error = false; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
if ($this->_in_transaction) |
323
|
|
|
$this->db_transaction('commit', $connection); |
|
|
|
|
324
|
|
|
|
325
|
|
|
// Debugging. |
326
|
|
View Code Duplication |
if ($db_show_debug === true) |
327
|
|
|
{ |
328
|
|
|
$db_cache['t'] = microtime(true) - $st; |
|
|
|
|
329
|
|
|
$debug->db_query($db_cache); |
|
|
|
|
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
return $this->_db_last_result; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* Affected rows from previous operation. |
337
|
|
|
* |
338
|
|
|
* @param resource|null $result |
339
|
|
|
*/ |
340
|
|
|
public function affected_rows($result = null) |
341
|
|
|
{ |
342
|
|
|
if ($this->_db_replace_result !== null) |
343
|
|
|
return $this->_db_replace_result; |
344
|
|
|
elseif ($result === null && !$this->_db_last_result) |
345
|
|
|
return 0; |
346
|
|
|
|
347
|
|
|
return pg_affected_rows($result === null ? $this->_db_last_result : $result); |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* Last inserted id. |
352
|
|
|
* |
353
|
|
|
* @param string $table |
354
|
|
|
* @param string|null $field = null |
355
|
|
|
* @param resource|null $connection = null |
356
|
|
|
* @throws Elk_Exception |
357
|
|
|
*/ |
358
|
|
|
public function insert_id($table, $field = null, $connection = null) |
359
|
|
|
{ |
360
|
|
|
global $db_prefix; |
361
|
|
|
|
362
|
|
|
$table = str_replace('{db_prefix}', $db_prefix, $table); |
363
|
|
|
|
364
|
|
|
$connection = $connection === null ? $this->_connection : $connection; |
365
|
|
|
|
366
|
|
|
// Try get the last ID for the auto increment field. |
367
|
|
|
$request = $this->query('', 'SELECT CURRVAL(\'' . $table . '_seq\') AS insertID', |
368
|
|
|
array( |
369
|
|
|
), |
370
|
|
|
$connection |
|
|
|
|
371
|
|
|
); |
372
|
|
|
|
373
|
|
|
if (!$request) |
374
|
|
|
return false; |
375
|
|
|
|
376
|
|
|
list ($lastID) = $this->fetch_row($request); |
|
|
|
|
377
|
|
|
$this->free_result($request); |
|
|
|
|
378
|
|
|
|
379
|
|
|
return $lastID; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* Tracking the current row. |
384
|
|
|
* Fetch a row from the resultset given as parameter. |
385
|
|
|
* |
386
|
|
|
* @param resource $request |
387
|
|
|
* @param integer|bool $counter = false |
388
|
|
|
*/ |
389
|
|
View Code Duplication |
public function fetch_row($request, $counter = false) |
390
|
|
|
{ |
391
|
|
|
global $db_row_count; |
392
|
|
|
|
393
|
|
|
if ($counter !== false) |
394
|
|
|
return pg_fetch_row($request, $counter); |
395
|
|
|
|
396
|
|
|
// Reset the row counter... |
397
|
|
|
if (!isset($db_row_count[(int) $request])) |
398
|
|
|
$db_row_count[(int) $request] = 0; |
399
|
|
|
|
400
|
|
|
// Return the right row. |
401
|
|
|
return @pg_fetch_row($request, $db_row_count[(int) $request]++); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
/** |
405
|
|
|
* Free the resultset. |
406
|
|
|
* |
407
|
|
|
* @param resource $result |
408
|
|
|
*/ |
409
|
|
|
public function free_result($result) |
410
|
|
|
{ |
411
|
|
|
// Just delegate to the native function |
412
|
|
|
pg_free_result($result); |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
/** |
416
|
|
|
* Get the number of rows in the result. |
417
|
|
|
* |
418
|
|
|
* @param resource $result |
419
|
|
|
*/ |
420
|
|
|
public function num_rows($result) |
421
|
|
|
{ |
422
|
|
|
// simply delegate to the native function |
423
|
|
|
return pg_num_rows($result); |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
/** |
427
|
|
|
* Get the number of fields in the resultset. |
428
|
|
|
* |
429
|
|
|
* @param resource $request |
430
|
|
|
*/ |
431
|
|
|
public function num_fields($request) |
432
|
|
|
{ |
433
|
|
|
return pg_num_fields($request); |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
/** |
437
|
|
|
* Reset the internal result pointer. |
438
|
|
|
* |
439
|
|
|
* @param boolean $request |
440
|
|
|
* @param integer $counter |
441
|
|
|
*/ |
442
|
|
|
public function data_seek($request, $counter) |
443
|
|
|
{ |
444
|
|
|
global $db_row_count; |
445
|
|
|
|
446
|
|
|
$db_row_count[(int) $request] = $counter; |
447
|
|
|
|
448
|
|
|
return true; |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
/** |
452
|
|
|
* Do a transaction. |
453
|
|
|
* |
454
|
|
|
* @param string $type - the step to perform (i.e. 'begin', 'commit', 'rollback') |
455
|
|
|
* @param resource|null $connection = null |
456
|
|
|
*/ |
457
|
|
|
public function db_transaction($type = 'commit', $connection = null) |
458
|
|
|
{ |
459
|
|
|
// Decide which connection to use |
460
|
|
|
$connection = $connection === null ? $this->_connection : $connection; |
461
|
|
|
|
462
|
|
|
if ($type == 'begin') |
463
|
|
|
{ |
464
|
|
|
$this->_in_transaction = true; |
465
|
|
|
return @pg_query($connection, 'BEGIN'); |
466
|
|
|
} |
467
|
|
|
elseif ($type == 'rollback') |
468
|
|
|
return @pg_query($connection, 'ROLLBACK'); |
469
|
|
|
elseif ($type == 'commit') |
470
|
|
|
{ |
471
|
|
|
$this->_in_transaction = false; |
472
|
|
|
return @pg_query($connection, 'COMMIT'); |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
return false; |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
/** |
479
|
|
|
* Return last error string from the database server |
480
|
|
|
* |
481
|
|
|
* @param resource|null $connection = null |
482
|
|
|
*/ |
483
|
|
|
public function last_error($connection = null) |
484
|
|
|
{ |
485
|
|
|
// Decide which connection to use |
486
|
|
|
$connection = $connection === null ? $this->_connection : $connection; |
487
|
|
|
|
488
|
|
|
if (is_resource($connection)) |
489
|
|
|
return pg_last_error($connection); |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* Database error. |
494
|
|
|
* Backtrace, log, try to fix. |
495
|
|
|
* |
496
|
|
|
* @param string $db_string |
497
|
|
|
* @param resource|null $connection = null |
498
|
|
|
* |
499
|
|
|
* @throws Elk_Exception |
500
|
|
|
*/ |
501
|
|
|
public function error($db_string, $connection = null) |
502
|
|
|
{ |
503
|
|
|
global $txt, $context, $modSettings, $db_show_debug; |
504
|
|
|
|
505
|
|
|
// We'll try recovering the file and line number the original db query was called from. |
506
|
|
|
list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__); |
507
|
|
|
|
508
|
|
|
// Decide which connection to use |
509
|
|
|
$connection = $connection === null ? $this->_connection : $connection; |
510
|
|
|
|
511
|
|
|
// This is the error message... |
512
|
|
|
$query_error = @pg_last_error($connection); |
513
|
|
|
|
514
|
|
|
// Log the error. |
515
|
|
View Code Duplication |
if (class_exists('Errors')) |
516
|
|
|
{ |
517
|
|
|
Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n" . $db_string : ''), 'database', $file, $line); |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
// Nothing's defined yet... just die with it. |
521
|
|
|
if (empty($context) || empty($txt)) |
522
|
|
|
die($query_error); |
|
|
|
|
523
|
|
|
|
524
|
|
|
// Show an error message, if possible. |
525
|
|
|
$context['error_title'] = $txt['database_error']; |
526
|
|
View Code Duplication |
if (allowedTo('admin_forum')) |
527
|
|
|
$context['error_message'] = nl2br($query_error) . '<br />' . $txt['file'] . ': ' . $file . '<br />' . $txt['line'] . ': ' . $line; |
528
|
|
|
else |
529
|
|
|
$context['error_message'] = $txt['try_again']; |
530
|
|
|
|
531
|
|
|
// Add database version that we know of, for the admin to know. (and ask for support) |
532
|
|
View Code Duplication |
if (allowedTo('admin_forum')) |
533
|
|
|
$context['error_message'] .= '<br /><br />' . sprintf($txt['database_error_versions'], $modSettings['elkVersion']); |
534
|
|
|
|
535
|
|
View Code Duplication |
if (allowedTo('admin_forum') && $db_show_debug === true) |
536
|
|
|
$context['error_message'] .= '<br /><br />' . nl2br($db_string); |
537
|
|
|
|
538
|
|
|
// It's already been logged... don't log it again. |
539
|
|
|
throw new Elk_Exception($context['error_message'], false); |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* Insert data. |
544
|
|
|
* |
545
|
|
|
* @param string $method - options 'replace', 'ignore', 'insert' |
546
|
|
|
* @param string $table |
547
|
|
|
* @param mixed[] $columns |
548
|
|
|
* @param mixed[] $data |
549
|
|
|
* @param mixed[] $keys |
550
|
|
|
* @param bool $disable_trans = false |
551
|
|
|
* @param resource|null $connection = null |
552
|
|
|
* @throws Elk_Exception |
553
|
|
|
*/ |
554
|
|
|
public function insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false, $connection = null) |
555
|
|
|
{ |
556
|
|
|
global $db_prefix; |
557
|
|
|
|
558
|
|
|
$connection = $connection === null ? $this->_connection : $connection; |
559
|
|
|
|
560
|
|
|
// With nothing to insert, simply return. |
561
|
|
|
if (empty($data)) |
562
|
|
|
return; |
563
|
|
|
|
564
|
|
|
// Inserting data as a single row can be done as a single array. |
565
|
|
|
if (!is_array($data[array_rand($data)])) |
566
|
|
|
$data = array($data); |
567
|
|
|
|
568
|
|
|
// Replace the prefix holder with the actual prefix. |
569
|
|
|
$table = str_replace('{db_prefix}', $db_prefix, $table); |
570
|
|
|
|
571
|
|
|
$priv_trans = false; |
572
|
|
|
if ((count($data) > 1 || $method == 'replace') && !$this->_in_transaction && !$disable_trans) |
573
|
|
|
{ |
574
|
|
|
$this->db_transaction('begin', $connection); |
|
|
|
|
575
|
|
|
$priv_trans = true; |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
// PostgreSQL doesn't support replace: we implement a MySQL-compatible behavior instead |
579
|
|
|
if ($method == 'replace') |
580
|
|
|
{ |
581
|
|
|
$count = 0; |
582
|
|
|
$where = ''; |
583
|
|
|
$db_replace_result = 0; |
584
|
|
|
foreach ($columns as $columnName => $type) |
585
|
|
|
{ |
586
|
|
|
// Are we restricting the length? |
587
|
|
|
if (strpos($type, 'string-') !== false) |
588
|
|
|
$actualType = sprintf($columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $count); |
589
|
|
|
else |
590
|
|
|
$actualType = sprintf($columnName . ' = {%1$s:%2$s}, ', $type, $count); |
591
|
|
|
|
592
|
|
|
// A key? That's what we were looking for. |
593
|
|
|
if (in_array($columnName, $keys)) |
594
|
|
|
$where .= (empty($where) ? '' : ' AND ') . substr($actualType, 0, -2); |
595
|
|
|
$count++; |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
// Make it so. |
599
|
|
|
if (!empty($where) && !empty($data)) |
600
|
|
|
{ |
601
|
|
|
foreach ($data as $k => $entry) |
602
|
|
|
{ |
603
|
|
|
$this->query('', ' |
604
|
|
|
DELETE FROM ' . $table . |
605
|
|
|
' WHERE ' . $where, |
606
|
|
|
$entry, $connection |
|
|
|
|
607
|
|
|
); |
608
|
|
|
$db_replace_result += (!$this->_db_last_result ? 0 : pg_affected_rows($this->_db_last_result)); |
609
|
|
|
} |
610
|
|
|
} |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
if (!empty($data)) |
614
|
|
|
{ |
615
|
|
|
// Create the mold for a single row insert. |
616
|
|
|
$insertData = '('; |
617
|
|
View Code Duplication |
foreach ($columns as $columnName => $type) |
618
|
|
|
{ |
619
|
|
|
// Are we restricting the length? |
620
|
|
|
if (strpos($type, 'string-') !== false) |
621
|
|
|
$insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName); |
622
|
|
|
else |
623
|
|
|
$insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName); |
624
|
|
|
} |
625
|
|
|
$insertData = substr($insertData, 0, -2) . ')'; |
626
|
|
|
|
627
|
|
|
// Create an array consisting of only the columns. |
628
|
|
|
$indexed_columns = array_keys($columns); |
629
|
|
|
|
630
|
|
|
// Here's where the variables are injected to the query. |
631
|
|
|
$insertRows = array(); |
632
|
|
|
foreach ($data as $dataRow) |
633
|
|
|
$insertRows[] = $this->quote($insertData, $this->_array_combine($indexed_columns, $dataRow), $connection); |
|
|
|
|
634
|
|
|
|
635
|
|
|
$inserted_results = 0; |
636
|
|
|
$skip_error = $method == 'ignore' || $table === $db_prefix . 'log_errors'; |
637
|
|
|
foreach ($insertRows as $entry) |
638
|
|
|
{ |
639
|
|
|
$this->_skip_error = $skip_error; |
640
|
|
|
|
641
|
|
|
// Do the insert. |
642
|
|
|
$this->query('', ' |
643
|
|
|
INSERT INTO ' . $table . '("' . implode('", "', $indexed_columns) . '") |
644
|
|
|
VALUES |
645
|
|
|
' . $entry, |
646
|
|
|
array( |
647
|
|
|
'security_override' => true, |
648
|
|
|
), |
649
|
|
|
$connection |
|
|
|
|
650
|
|
|
); |
651
|
|
|
$inserted_results += (!$this->_db_last_result ? 0 : pg_affected_rows($this->_db_last_result)); |
652
|
|
|
} |
653
|
|
|
if (isset($db_replace_result)) |
654
|
|
|
$this->_db_replace_result = $db_replace_result + $inserted_results; |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
if ($priv_trans) |
658
|
|
|
$this->db_transaction('commit', $connection); |
|
|
|
|
659
|
|
|
} |
660
|
|
|
|
661
|
|
|
/** |
662
|
|
|
* Unescape an escaped string! |
663
|
|
|
* |
664
|
|
|
* @param string $string |
665
|
|
|
*/ |
666
|
|
|
public function unescape_string($string) |
667
|
|
|
{ |
668
|
|
|
return strtr($string, array('\'\'' => '\'')); |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
/** |
672
|
|
|
* Returns whether the database system supports ignore. |
673
|
|
|
* |
674
|
|
|
* @return false |
675
|
|
|
*/ |
676
|
|
|
public function support_ignore() |
677
|
|
|
{ |
678
|
|
|
return false; |
679
|
|
|
} |
680
|
|
|
|
681
|
|
|
/** |
682
|
|
|
* Gets all the necessary INSERTs for the table named table_name. |
683
|
|
|
* It goes in 250 row segments. |
684
|
|
|
* |
685
|
|
|
* @param string $tableName - the table to create the inserts for. |
686
|
|
|
* @param bool $new_table |
687
|
|
|
* |
688
|
|
|
* @return string the query to insert the data back in, or an empty string if the table was empty. |
689
|
|
|
* @throws Elk_Exception |
690
|
|
|
*/ |
691
|
|
|
public function insert_sql($tableName, $new_table = false) |
692
|
|
|
{ |
693
|
|
|
global $db_prefix; |
694
|
|
|
|
695
|
|
|
static $start = 0, $num_rows, $fields, $limit; |
696
|
|
|
|
697
|
|
View Code Duplication |
if ($new_table) |
698
|
|
|
{ |
699
|
|
|
$limit = strstr($tableName, 'log_') !== false ? 500 : 250; |
700
|
|
|
$start = 0; |
701
|
|
|
} |
702
|
|
|
|
703
|
|
|
$data = ''; |
704
|
|
|
$tableName = str_replace('{db_prefix}', $db_prefix, $tableName); |
705
|
|
|
|
706
|
|
|
// This will be handy... |
707
|
|
|
$crlf = "\r\n"; |
708
|
|
|
|
709
|
|
|
$result = $this->query('', ' |
710
|
|
|
SELECT * |
711
|
|
|
FROM ' . $tableName . ' |
712
|
|
|
LIMIT ' . $start . ', ' . $limit, |
713
|
|
|
array( |
714
|
|
|
'security_override' => true, |
715
|
|
|
) |
716
|
|
|
); |
717
|
|
|
|
718
|
|
|
// The number of rows, just for record keeping and breaking INSERTs up. |
719
|
|
|
$num_rows = $this->num_rows($result); |
|
|
|
|
720
|
|
|
|
721
|
|
|
if ($num_rows == 0) |
722
|
|
|
return ''; |
723
|
|
|
|
724
|
|
|
if ($new_table) |
725
|
|
|
{ |
726
|
|
|
$fields = array_keys($this->fetch_assoc($result)); |
|
|
|
|
727
|
|
|
$this->data_seek($result, 0); |
|
|
|
|
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
// Start it off with the basic INSERT INTO. |
731
|
|
|
$insert_msg = 'INSERT INTO ' . $tableName . $crlf . "\t" . '(' . implode(', ', $fields) . ')' . $crlf . 'VALUES ' . $crlf . "\t"; |
732
|
|
|
|
733
|
|
|
// Loop through each row. |
734
|
|
View Code Duplication |
while ($row = $this->fetch_assoc($result)) |
|
|
|
|
735
|
|
|
{ |
736
|
|
|
// Get the fields in this row... |
737
|
|
|
$field_list = array(); |
738
|
|
|
|
739
|
|
|
foreach ($row as $key => $item) |
740
|
|
|
{ |
741
|
|
|
// Try to figure out the type of each field. (NULL, number, or 'string'.) |
742
|
|
|
if (!isset($item)) |
743
|
|
|
$field_list[] = 'NULL'; |
744
|
|
|
elseif (is_numeric($item) && (int) $item == $item) |
745
|
|
|
$field_list[] = $item; |
746
|
|
|
else |
747
|
|
|
$field_list[] = '\'' . $this->escape_string($item) . '\''; |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
// 'Insert' the data. |
751
|
|
|
$data .= $insert_msg . '(' . implode(', ', $field_list) . ');' . $crlf; |
752
|
|
|
} |
753
|
|
|
$this->free_result($result); |
|
|
|
|
754
|
|
|
|
755
|
|
|
$data .= $crlf; |
756
|
|
|
|
757
|
|
|
$start += $limit; |
758
|
|
|
|
759
|
|
|
return $data; |
760
|
|
|
} |
761
|
|
|
|
762
|
|
|
/** |
763
|
|
|
* Dumps the schema (CREATE) for a table. |
764
|
|
|
* |
765
|
|
|
* @param string $tableName - the table |
766
|
|
|
* |
767
|
|
|
* @return string - the CREATE statement as string |
768
|
|
|
* @throws Elk_Exception |
769
|
|
|
*/ |
770
|
|
|
public function db_table_sql($tableName) |
771
|
|
|
{ |
772
|
|
|
global $db_prefix; |
773
|
|
|
|
774
|
|
|
$tableName = str_replace('{db_prefix}', $db_prefix, $tableName); |
775
|
|
|
|
776
|
|
|
// This will be needed... |
777
|
|
|
$crlf = "\r\n"; |
778
|
|
|
|
779
|
|
|
// Start the create table... |
780
|
|
|
$schema_create = 'CREATE TABLE ' . $tableName . ' (' . $crlf; |
781
|
|
|
$index_create = ''; |
782
|
|
|
$seq_create = ''; |
783
|
|
|
|
784
|
|
|
// Find all the fields. |
785
|
|
|
$result = $this->query('', ' |
786
|
|
|
SELECT column_name, column_default, is_nullable, data_type, character_maximum_length |
787
|
|
|
FROM information_schema.columns |
788
|
|
|
WHERE table_name = {string:table} |
789
|
|
|
ORDER BY ordinal_position', |
790
|
|
|
array( |
791
|
|
|
'table' => $tableName, |
792
|
|
|
) |
793
|
|
|
); |
794
|
|
|
while ($row = $this->fetch_assoc($result)) |
|
|
|
|
795
|
|
|
{ |
796
|
|
|
if ($row['data_type'] == 'character varying') |
797
|
|
|
$row['data_type'] = 'varchar'; |
798
|
|
|
elseif ($row['data_type'] == 'character') |
799
|
|
|
$row['data_type'] = 'char'; |
800
|
|
|
|
801
|
|
|
if ($row['character_maximum_length']) |
802
|
|
|
$row['data_type'] .= '(' . $row['character_maximum_length'] . ')'; |
803
|
|
|
|
804
|
|
|
// Make the CREATE for this column. |
805
|
|
|
$schema_create .= ' "' . $row['column_name'] . '" ' . $row['data_type'] . ($row['is_nullable'] != 'YES' ? ' NOT NULL' : ''); |
806
|
|
|
|
807
|
|
|
// Add a default...? |
808
|
|
|
if (trim($row['column_default']) != '') |
809
|
|
|
{ |
810
|
|
|
$schema_create .= ' default ' . $row['column_default'] . ''; |
811
|
|
|
|
812
|
|
|
// Auto increment? |
813
|
|
|
if (preg_match('~nextval\(\'(.+?)\'(.+?)*\)~i', $row['column_default'], $matches) != 0) |
814
|
|
|
{ |
815
|
|
|
// Get to find the next variable first! |
816
|
|
|
$count_req = $this->query('', ' |
817
|
|
|
SELECT MAX("{raw:column}") |
818
|
|
|
FROM {raw:table}', |
819
|
|
|
array( |
820
|
|
|
'column' => $row['column_name'], |
821
|
|
|
'table' => $tableName, |
822
|
|
|
) |
823
|
|
|
); |
824
|
|
|
list ($max_ind) = $this->fetch_row($count_req); |
|
|
|
|
825
|
|
|
$this->free_result($count_req); |
|
|
|
|
826
|
|
|
|
827
|
|
|
// Get the right bloody start! |
828
|
|
|
$seq_create .= 'CREATE SEQUENCE ' . $matches[1] . ' START WITH ' . ($max_ind + 1) . ';' . $crlf . $crlf; |
829
|
|
|
} |
830
|
|
|
} |
831
|
|
|
|
832
|
|
|
$schema_create .= ',' . $crlf; |
833
|
|
|
} |
834
|
|
|
$this->free_result($result); |
|
|
|
|
835
|
|
|
|
836
|
|
|
// Take off the last comma. |
837
|
|
|
$schema_create = substr($schema_create, 0, -strlen($crlf) - 1); |
838
|
|
|
|
839
|
|
|
$result = $this->query('', ' |
840
|
|
|
SELECT CASE WHEN i.indisprimary THEN 1 ELSE 0 END AS is_primary, pg_get_indexdef(i.indexrelid) AS inddef |
841
|
|
|
FROM pg_class AS c |
842
|
|
|
INNER JOIN pg_index AS i ON (i.indrelid = c.oid) |
843
|
|
|
INNER JOIN pg_class AS c2 ON (c2.oid = i.indexrelid) |
844
|
|
|
WHERE c.relname = {string:table}', |
845
|
|
|
array( |
846
|
|
|
'table' => $tableName, |
847
|
|
|
) |
848
|
|
|
); |
849
|
|
|
|
850
|
|
|
while ($row = $this->fetch_assoc($result)) |
|
|
|
|
851
|
|
|
{ |
852
|
|
|
if ($row['is_primary']) |
853
|
|
|
{ |
854
|
|
|
if (preg_match('~\(([^\)]+?)\)~i', $row['inddef'], $matches) == 0) |
855
|
|
|
continue; |
856
|
|
|
|
857
|
|
|
$index_create .= $crlf . 'ALTER TABLE ' . $tableName . ' ADD PRIMARY KEY ("' . $matches[1] . '");'; |
858
|
|
|
} |
859
|
|
|
else |
860
|
|
|
$index_create .= $crlf . $row['inddef'] . ';'; |
861
|
|
|
} |
862
|
|
|
$this->free_result($result); |
|
|
|
|
863
|
|
|
|
864
|
|
|
// Finish it off! |
865
|
|
|
$schema_create .= $crlf . ');'; |
866
|
|
|
|
867
|
|
|
return $seq_create . $schema_create . $index_create; |
868
|
|
|
} |
869
|
|
|
|
870
|
|
|
/** |
871
|
|
|
* {@inheritdoc} |
872
|
|
|
*/ |
873
|
|
|
public function db_list_tables($db_name_str = false, $filter = false) |
874
|
|
|
{ |
875
|
|
|
$request = $this->query('', ' |
876
|
|
|
SELECT tablename |
877
|
|
|
FROM pg_tables |
878
|
|
|
WHERE schemaname = {string:schema_public}' . ($filter === false ? '' : ' |
879
|
|
|
AND tablename LIKE {string:filter}') . ' |
880
|
|
|
ORDER BY tablename', |
881
|
|
|
array( |
882
|
|
|
'schema_public' => 'public', |
883
|
|
|
'filter' => $filter, |
884
|
|
|
) |
885
|
|
|
); |
886
|
|
|
$tables = array(); |
887
|
|
|
while ($row = $this->fetch_row($request)) |
|
|
|
|
888
|
|
|
$tables[] = $row[0]; |
889
|
|
|
$this->free_result($request); |
|
|
|
|
890
|
|
|
|
891
|
|
|
return $tables; |
892
|
|
|
} |
893
|
|
|
|
894
|
|
|
/** |
895
|
|
|
* Backup $table to $backup_table. |
896
|
|
|
* |
897
|
|
|
* @param string $table |
898
|
|
|
* @param string $backup_table |
899
|
|
|
* @throws Elk_Exception |
900
|
|
|
*/ |
901
|
|
|
public function db_backup_table($table, $backup_table) |
902
|
|
|
{ |
903
|
|
|
global $db_prefix; |
904
|
|
|
|
905
|
|
|
$table = str_replace('{db_prefix}', $db_prefix, $table); |
906
|
|
|
|
907
|
|
|
// Do we need to drop it first? |
908
|
|
|
$db_table = db_table(); |
909
|
|
|
$db_table->db_drop_table($backup_table); |
910
|
|
|
|
911
|
|
|
// @todo Should we create backups of sequences as well? |
912
|
|
|
$this->query('', ' |
913
|
|
|
CREATE TABLE {raw:backup_table} |
914
|
|
|
( |
915
|
|
|
LIKE {raw:table} |
916
|
|
|
INCLUDING DEFAULTS |
917
|
|
|
)', |
918
|
|
|
array( |
919
|
|
|
'backup_table' => $backup_table, |
920
|
|
|
'table' => $table, |
921
|
|
|
) |
922
|
|
|
); |
923
|
|
|
|
924
|
|
|
$this->query('', ' |
925
|
|
|
INSERT INTO {raw:backup_table} |
926
|
|
|
SELECT * FROM {raw:table}', |
927
|
|
|
array( |
928
|
|
|
'backup_table' => $backup_table, |
929
|
|
|
'table' => $table, |
930
|
|
|
) |
931
|
|
|
); |
932
|
|
|
} |
933
|
|
|
|
934
|
|
|
/** |
935
|
|
|
* Get the server version number. |
936
|
|
|
* |
937
|
|
|
* @return string - the version |
938
|
|
|
*/ |
939
|
|
|
public function db_server_version() |
940
|
|
|
{ |
941
|
|
|
$version = pg_version(); |
942
|
|
|
|
943
|
|
|
return $version['server']; |
944
|
|
|
} |
945
|
|
|
|
946
|
|
|
/** |
947
|
|
|
* Get the name (title) of the database system. |
948
|
|
|
* |
949
|
|
|
* @return string |
950
|
|
|
*/ |
951
|
|
|
public function db_title() |
952
|
|
|
{ |
953
|
|
|
return 'PostgreSQL'; |
954
|
|
|
} |
955
|
|
|
|
956
|
|
|
/** |
957
|
|
|
* Whether the database system is case sensitive. |
958
|
|
|
* |
959
|
|
|
* @return boolean |
960
|
|
|
*/ |
961
|
|
|
public function db_case_sensitive() |
962
|
|
|
{ |
963
|
|
|
return true; |
964
|
|
|
} |
965
|
|
|
|
966
|
|
|
/** |
967
|
|
|
* Quotes identifiers for replacement__callback. |
968
|
|
|
* |
969
|
|
|
* @param mixed $replacement |
970
|
|
|
* @return string |
971
|
|
|
* @throws Elk_Exception |
972
|
|
|
*/ |
973
|
|
View Code Duplication |
protected function _replaceIdentifier($replacement) |
974
|
|
|
{ |
975
|
|
|
if (preg_match('~[a-z_][0-9,a-z,A-Z$_]{0,60}~', $replacement) !== 1) |
976
|
|
|
{ |
977
|
|
|
$this->error_backtrace('Wrong value type sent to the database. Invalid identifier used. (' . $replacement . ')', '', E_USER_ERROR, __FILE__, __LINE__); |
978
|
|
|
} |
979
|
|
|
|
980
|
|
|
return '"' . $replacement . '"'; |
981
|
|
|
} |
982
|
|
|
|
983
|
|
|
/** |
984
|
|
|
* Escape string for the database input |
985
|
|
|
* |
986
|
|
|
* @param string $string |
987
|
|
|
*/ |
988
|
|
|
public function escape_string($string) |
989
|
|
|
{ |
990
|
|
|
return pg_escape_string($string); |
991
|
|
|
} |
992
|
|
|
|
993
|
|
|
/** |
994
|
|
|
* Fetch next result as association. |
995
|
|
|
* |
996
|
|
|
* @param resource $request |
997
|
|
|
* @param int|bool $counter = false |
998
|
|
|
*/ |
999
|
|
View Code Duplication |
public function fetch_assoc($request, $counter = false) |
1000
|
|
|
{ |
1001
|
|
|
global $db_row_count; |
1002
|
|
|
|
1003
|
|
|
if ($counter !== false) |
1004
|
|
|
return pg_fetch_assoc($request, $counter); |
1005
|
|
|
|
1006
|
|
|
// Reset the row counter... |
1007
|
|
|
if (!isset($db_row_count[(int) $request])) |
1008
|
|
|
$db_row_count[(int) $request] = 0; |
1009
|
|
|
|
1010
|
|
|
// Return the right row. |
1011
|
|
|
return @pg_fetch_assoc($request, $db_row_count[(int) $request]++); |
1012
|
|
|
} |
1013
|
|
|
|
1014
|
|
|
/** |
1015
|
|
|
* Return server info. |
1016
|
|
|
* |
1017
|
|
|
* @return string |
1018
|
|
|
*/ |
1019
|
|
|
public function db_server_info() |
1020
|
|
|
{ |
1021
|
|
|
// give info on client! we use it in install and upgrade and such things. |
1022
|
|
|
$version = pg_version(); |
1023
|
|
|
|
1024
|
|
|
return $version['client']; |
1025
|
|
|
} |
1026
|
|
|
|
1027
|
|
|
/** |
1028
|
|
|
* Return client version. |
1029
|
|
|
* |
1030
|
|
|
* @return string - the version |
1031
|
|
|
*/ |
1032
|
|
|
public function db_client_version() |
1033
|
|
|
{ |
1034
|
|
|
$version = pg_version(); |
1035
|
|
|
|
1036
|
|
|
return $version['client']; |
1037
|
|
|
} |
1038
|
|
|
|
1039
|
|
|
/** |
1040
|
|
|
* Dummy function really. Doesn't do anything on PostgreSQL. |
1041
|
|
|
* |
1042
|
|
|
* @param string|null $db_name = null |
1043
|
|
|
* @param resource|null $connection = null |
1044
|
|
|
* |
1045
|
|
|
* @return boolean |
1046
|
|
|
*/ |
1047
|
|
|
public function select_db($db_name = null, $connection = null) |
1048
|
|
|
{ |
1049
|
|
|
return true; |
1050
|
|
|
} |
1051
|
|
|
|
1052
|
|
|
/** |
1053
|
|
|
* Returns a reference to the existing instance |
1054
|
|
|
*/ |
1055
|
|
|
public static function db() |
1056
|
|
|
{ |
1057
|
|
|
return self::$_db; |
1058
|
|
|
} |
1059
|
|
|
|
1060
|
|
|
/** |
1061
|
|
|
* Finds out if the connection is still valid. |
1062
|
|
|
* |
1063
|
|
|
* @param postgre|null $connection = null |
1064
|
|
|
*/ |
1065
|
|
|
public function validConnection($connection = null) |
1066
|
|
|
{ |
1067
|
|
|
return is_resource($connection); |
1068
|
|
|
} |
1069
|
|
|
} |
1070
|
|
|
|
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.