Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 178 |
|
0.00% |
0 / 18 |
CRAP | |
0.00% |
0 / 1 |
postgres | |
0.00% |
0 / 178 |
|
0.00% |
0 / 18 |
9900 | |
0.00% |
0 / 1 |
sql_connect | |
0.00% |
0 / 39 |
|
0.00% |
0 / 1 |
272 | |||
sql_server_info | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
90 | |||
_sql_transaction | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
sql_query | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
272 | |||
_sql_query_limit | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
sql_affectedrows | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
sql_fetchrow | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
sql_rowseek | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
sql_fetchfield | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
72 | |||
sql_last_inserted_id | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
42 | |||
sql_freeresult | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
42 | |||
sql_escape | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
cast_expr_to_bigint | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
cast_expr_to_string | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
_sql_error | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
_sql_close | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
_sql_report | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
132 | |||
sql_quote | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * |
4 | * This file is part of the phpBB Forum Software package. |
5 | * |
6 | * @copyright (c) phpBB Limited <https://www.phpbb.com> |
7 | * @license GNU General Public License, version 2 (GPL-2.0) |
8 | * |
9 | * For full copyright and license information, please see |
10 | * the docs/CREDITS.txt file. |
11 | * |
12 | */ |
13 | |
14 | namespace phpbb\db\driver; |
15 | |
16 | /** |
17 | * PostgreSQL Database Abstraction Layer |
18 | * Minimum Requirement is Version 8.3+ |
19 | */ |
20 | class postgres extends \phpbb\db\driver\driver |
21 | { |
22 | var $multi_insert = true; |
23 | var $connect_error = ''; |
24 | |
25 | /** |
26 | * {@inheritDoc} |
27 | */ |
28 | function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) |
29 | { |
30 | $connect_string = ''; |
31 | |
32 | if ($sqluser) |
33 | { |
34 | $connect_string .= "user=$sqluser "; |
35 | } |
36 | |
37 | if ($sqlpassword) |
38 | { |
39 | $connect_string .= "password='$sqlpassword' "; |
40 | } |
41 | |
42 | if ($sqlserver) |
43 | { |
44 | // $sqlserver can carry a port separated by : for compatibility reasons |
45 | // If $sqlserver has more than one : it's probably an IPv6 address. |
46 | // In this case we only allow passing a port via the $port variable. |
47 | if (substr_count($sqlserver, ':') === 1) |
48 | { |
49 | list($sqlserver, $port) = explode(':', $sqlserver); |
50 | } |
51 | |
52 | if ($sqlserver !== 'localhost') |
53 | { |
54 | $connect_string .= "host=$sqlserver "; |
55 | } |
56 | |
57 | if ($port) |
58 | { |
59 | $connect_string .= "port=$port "; |
60 | } |
61 | } |
62 | |
63 | $schema = ''; |
64 | |
65 | if ($database) |
66 | { |
67 | $this->dbname = $database; |
68 | if (strpos($database, '.') !== false) |
69 | { |
70 | list($database, $schema) = explode('.', $database); |
71 | } |
72 | $connect_string .= "dbname=$database"; |
73 | } |
74 | |
75 | $this->persistency = $persistency; |
76 | |
77 | if ($this->persistency) |
78 | { |
79 | if (!function_exists('pg_pconnect')) |
80 | { |
81 | $this->connect_error = 'pg_pconnect function does not exist, is pgsql extension installed?'; |
82 | return $this->sql_error(''); |
83 | } |
84 | $collector = new \phpbb\error_collector; |
85 | $collector->install(); |
86 | $this->db_connect_id = (!$new_link) ? @pg_pconnect($connect_string) : @pg_pconnect($connect_string, PGSQL_CONNECT_FORCE_NEW); |
87 | } |
88 | else |
89 | { |
90 | if (!function_exists('pg_connect')) |
91 | { |
92 | $this->connect_error = 'pg_connect function does not exist, is pgsql extension installed?'; |
93 | return $this->sql_error(''); |
94 | } |
95 | $collector = new \phpbb\error_collector; |
96 | $collector->install(); |
97 | $this->db_connect_id = (!$new_link) ? @pg_connect($connect_string) : @pg_connect($connect_string, PGSQL_CONNECT_FORCE_NEW); |
98 | } |
99 | |
100 | $collector->uninstall(); |
101 | |
102 | if ($this->db_connect_id) |
103 | { |
104 | if ($schema !== '') |
105 | { |
106 | @pg_query($this->db_connect_id, 'SET search_path TO ' . $schema); |
107 | } |
108 | return $this->db_connect_id; |
109 | } |
110 | |
111 | $this->connect_error = $collector->format_errors(); |
112 | return $this->sql_error(''); |
113 | } |
114 | |
115 | /** |
116 | * {@inheritDoc} |
117 | */ |
118 | function sql_server_info($raw = false, $use_cache = true) |
119 | { |
120 | global $cache; |
121 | |
122 | if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('pgsql_version')) === false) |
123 | { |
124 | $query_id = @pg_query($this->db_connect_id, 'SELECT VERSION() AS version'); |
125 | if ($query_id) |
126 | { |
127 | $row = pg_fetch_assoc($query_id, null); |
128 | pg_free_result($query_id); |
129 | |
130 | $this->sql_server_version = (!empty($row['version'])) ? trim(substr($row['version'], 10)) : 0; |
131 | |
132 | if (!empty($cache) && $use_cache) |
133 | { |
134 | $cache->put('pgsql_version', $this->sql_server_version); |
135 | } |
136 | } |
137 | } |
138 | |
139 | return ($raw) ? (string) $this->sql_server_version : 'PostgreSQL ' . $this->sql_server_version; |
140 | } |
141 | |
142 | /** |
143 | * {@inheritDoc} |
144 | */ |
145 | protected function _sql_transaction(string $status = 'begin'): bool |
146 | { |
147 | switch ($status) |
148 | { |
149 | case 'begin': |
150 | return @pg_query($this->db_connect_id, 'BEGIN') !== false; |
151 | |
152 | case 'commit': |
153 | return @pg_query($this->db_connect_id, 'COMMIT') !== false; |
154 | |
155 | case 'rollback': |
156 | return @pg_query($this->db_connect_id, 'ROLLBACK') !== false; |
157 | } |
158 | |
159 | return true; |
160 | } |
161 | |
162 | /** |
163 | * {@inheritDoc} |
164 | */ |
165 | function sql_query($query = '', $cache_ttl = 0) |
166 | { |
167 | if ($query != '') |
168 | { |
169 | global $cache; |
170 | |
171 | if ($this->debug_sql_explain) |
172 | { |
173 | $this->sql_report('start', $query); |
174 | } |
175 | else if ($this->debug_load_time) |
176 | { |
177 | $this->curtime = microtime(true); |
178 | } |
179 | |
180 | $this->last_query_text = $query; |
181 | $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; |
182 | $this->sql_add_num_queries($this->query_result); |
183 | |
184 | if ($this->query_result === false) |
185 | { |
186 | try |
187 | { |
188 | $this->query_result = @pg_query($this->db_connect_id, $query); |
189 | } |
190 | catch (\Error $e) |
191 | { |
192 | // Do nothing as SQL driver will report the error |
193 | } |
194 | |
195 | if ($this->query_result === false) |
196 | { |
197 | $this->sql_error($query); |
198 | } |
199 | |
200 | if ($this->debug_sql_explain) |
201 | { |
202 | $this->sql_report('stop', $query); |
203 | } |
204 | else if ($this->debug_load_time) |
205 | { |
206 | $this->sql_time += microtime(true) - $this->curtime; |
207 | } |
208 | |
209 | if (!$this->query_result) |
210 | { |
211 | return false; |
212 | } |
213 | |
214 | $safe_query_id = $this->clean_query_id($this->query_result); |
215 | |
216 | if ($cache && $cache_ttl) |
217 | { |
218 | $this->open_queries[$safe_query_id] = $this->query_result; |
219 | $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); |
220 | } |
221 | else if (strpos($query, 'SELECT') === 0) |
222 | { |
223 | $this->open_queries[$safe_query_id] = $this->query_result; |
224 | } |
225 | } |
226 | else if ($this->debug_sql_explain) |
227 | { |
228 | $this->sql_report('fromcache', $query); |
229 | } |
230 | } |
231 | else |
232 | { |
233 | return false; |
234 | } |
235 | |
236 | return $this->query_result; |
237 | } |
238 | |
239 | /** |
240 | * {@inheritDoc} |
241 | */ |
242 | protected function _sql_query_limit(string $query, int $total, int $offset = 0, int $cache_ttl = 0) |
243 | { |
244 | $this->query_result = false; |
245 | |
246 | // if $total is set to 0 we do not want to limit the number of rows |
247 | if ($total == 0) |
248 | { |
249 | $total = 'ALL'; |
250 | } |
251 | |
252 | $query .= "\n LIMIT $total OFFSET $offset"; |
253 | |
254 | return $this->sql_query($query, $cache_ttl); |
255 | } |
256 | |
257 | /** |
258 | * {@inheritDoc} |
259 | */ |
260 | function sql_affectedrows() |
261 | { |
262 | return ($this->query_result) ? @pg_affected_rows($this->query_result) : false; |
263 | } |
264 | |
265 | /** |
266 | * {@inheritDoc} |
267 | */ |
268 | function sql_fetchrow($query_id = false) |
269 | { |
270 | global $cache; |
271 | |
272 | if ($query_id === false) |
273 | { |
274 | $query_id = $this->query_result; |
275 | } |
276 | |
277 | $safe_query_id = $this->clean_query_id($query_id); |
278 | if ($cache && $cache->sql_exists($safe_query_id)) |
279 | { |
280 | return $cache->sql_fetchrow($safe_query_id); |
281 | } |
282 | |
283 | return ($query_id) ? pg_fetch_assoc($query_id, null) : false; |
284 | } |
285 | |
286 | /** |
287 | * {@inheritDoc} |
288 | */ |
289 | function sql_rowseek($rownum, &$query_id) |
290 | { |
291 | global $cache; |
292 | |
293 | if ($query_id === false) |
294 | { |
295 | $query_id = $this->query_result; |
296 | } |
297 | |
298 | $safe_query_id = $this->clean_query_id($query_id); |
299 | if ($cache && $cache->sql_exists($safe_query_id)) |
300 | { |
301 | return $cache->sql_rowseek($rownum, $safe_query_id); |
302 | } |
303 | |
304 | return ($query_id) ? @pg_result_seek($query_id, $rownum) : false; |
305 | } |
306 | |
307 | /** |
308 | * {@inheritDoc} |
309 | */ |
310 | function sql_fetchfield($field, $rownum = false, &$query_id = false) |
311 | { |
312 | global $cache; |
313 | |
314 | if ($query_id === false) |
315 | { |
316 | $query_id = $this->query_result; |
317 | } |
318 | |
319 | if ($query_id) |
320 | { |
321 | if ($rownum !== false) |
322 | { |
323 | $this->sql_rowseek($rownum, $query_id); |
324 | } |
325 | |
326 | $safe_query_id = $this->clean_query_id($query_id); |
327 | if ($cache && !is_object($query_id) && $cache->sql_exists($safe_query_id)) |
328 | { |
329 | return $cache->sql_fetchfield($safe_query_id, $field); |
330 | } |
331 | |
332 | $row = $this->sql_fetchrow($query_id); |
333 | return (isset($row[$field])) ? $row[$field] : false; |
334 | } |
335 | |
336 | return false; |
337 | } |
338 | |
339 | /** |
340 | * {@inheritdoc} |
341 | */ |
342 | public function sql_last_inserted_id() |
343 | { |
344 | $query_id = $this->query_result; |
345 | |
346 | if ($query_id !== false && $this->last_query_text != '') |
347 | { |
348 | if (preg_match("/^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)/is", $this->last_query_text, $tablename)) |
349 | { |
350 | $query = "SELECT currval('" . $tablename[1] . "_seq') AS last_value"; |
351 | $temp_q_id = @pg_query($this->db_connect_id, $query); |
352 | |
353 | if (!$temp_q_id) |
354 | { |
355 | return false; |
356 | } |
357 | |
358 | $temp_result = pg_fetch_assoc($temp_q_id, null); |
359 | pg_free_result($query_id); |
360 | |
361 | return ($temp_result) ? $temp_result['last_value'] : false; |
362 | } |
363 | } |
364 | |
365 | return false; |
366 | } |
367 | |
368 | /** |
369 | * {@inheritDoc} |
370 | */ |
371 | function sql_freeresult($query_id = false) |
372 | { |
373 | global $cache; |
374 | |
375 | if ($query_id === false) |
376 | { |
377 | $query_id = $this->query_result; |
378 | } |
379 | |
380 | $safe_query_id = $this->clean_query_id($query_id); |
381 | if ($cache && !is_object($query_id) && $cache->sql_exists($safe_query_id)) |
382 | { |
383 | $cache->sql_freeresult($safe_query_id); |
384 | } |
385 | else if (isset($this->open_queries[$safe_query_id])) |
386 | { |
387 | unset($this->open_queries[$safe_query_id]); |
388 | pg_free_result($query_id); |
389 | } |
390 | } |
391 | |
392 | /** |
393 | * {@inheritDoc} |
394 | */ |
395 | function sql_escape($msg) |
396 | { |
397 | return @pg_escape_string($msg); |
398 | } |
399 | |
400 | /** |
401 | * {@inheritDoc} |
402 | */ |
403 | function cast_expr_to_bigint($expression) |
404 | { |
405 | return 'CAST(' . $expression . ' as DECIMAL(255, 0))'; |
406 | } |
407 | |
408 | /** |
409 | * {@inheritDoc} |
410 | */ |
411 | function cast_expr_to_string($expression) |
412 | { |
413 | return 'CAST(' . $expression . ' as VARCHAR(255))'; |
414 | } |
415 | |
416 | /** |
417 | * {@inheritDoc} |
418 | */ |
419 | protected function _sql_error(): array |
420 | { |
421 | // pg_last_error only works when there is an established connection. |
422 | // Connection errors have to be tracked by us manually. |
423 | if ($this->db_connect_id) |
424 | { |
425 | $message = @pg_last_error($this->db_connect_id); |
426 | } |
427 | else |
428 | { |
429 | $message = $this->connect_error; |
430 | } |
431 | |
432 | return array( |
433 | 'message' => $message, |
434 | 'code' => '' |
435 | ); |
436 | } |
437 | |
438 | /** |
439 | * {@inheritDoc} |
440 | */ |
441 | protected function _sql_close(): bool |
442 | { |
443 | // Skip if connection is already closed or not persistent |
444 | if (!$this->persistency || !$this->db_connect_id instanceof \PgSql\Connection) |
445 | { |
446 | return true; |
447 | } |
448 | return pg_close($this->db_connect_id); |
449 | } |
450 | |
451 | /** |
452 | * {@inheritDoc} |
453 | */ |
454 | protected function _sql_report(string $mode, string $query = ''): void |
455 | { |
456 | switch ($mode) |
457 | { |
458 | case 'start': |
459 | |
460 | $explain_query = $query; |
461 | if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) |
462 | { |
463 | $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; |
464 | } |
465 | else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) |
466 | { |
467 | $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; |
468 | } |
469 | |
470 | if (preg_match('/^SELECT/', $explain_query)) |
471 | { |
472 | $html_table = false; |
473 | |
474 | if ($result = @pg_query($this->db_connect_id, "EXPLAIN $explain_query")) |
475 | { |
476 | while ($row = pg_fetch_assoc($result, null)) |
477 | { |
478 | $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); |
479 | } |
480 | pg_free_result($result); |
481 | } |
482 | |
483 | if ($html_table) |
484 | { |
485 | $this->html_hold .= '</table>'; |
486 | } |
487 | } |
488 | |
489 | break; |
490 | |
491 | case 'fromcache': |
492 | $endtime = explode(' ', microtime()); |
493 | $endtime = $endtime[0] + $endtime[1]; |
494 | |
495 | $result = @pg_query($this->db_connect_id, $query); |
496 | if ($result) |
497 | { |
498 | while ($void = pg_fetch_assoc($result, null)) |
499 | { |
500 | // Take the time spent on parsing rows into account |
501 | } |
502 | pg_free_result($result); |
503 | } |
504 | |
505 | $splittime = explode(' ', microtime()); |
506 | $splittime = $splittime[0] + $splittime[1]; |
507 | |
508 | $this->sql_report('record_fromcache', $query, $endtime, $splittime); |
509 | |
510 | break; |
511 | } |
512 | } |
513 | |
514 | /** |
515 | * {@inheritDoc} |
516 | */ |
517 | function sql_quote($msg) |
518 | { |
519 | return '"' . $msg . '"'; |
520 | } |
521 | } |