Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 178
0.00% covered (danger)
0.00%
0 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
postgres
0.00% covered (danger)
0.00%
0 / 178
0.00% covered (danger)
0.00%
0 / 18
9900
0.00% covered (danger)
0.00%
0 / 1
 sql_connect
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
272
 sql_server_info
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
90
 _sql_transaction
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 sql_query
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
272
 _sql_query_limit
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 sql_affectedrows
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 sql_fetchrow
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 sql_rowseek
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 sql_fetchfield
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
72
 sql_last_inserted_id
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 sql_freeresult
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 sql_escape
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 cast_expr_to_bigint
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 cast_expr_to_string
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 _sql_error
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 _sql_close
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 _sql_report
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
132
 sql_quote
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
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
14namespace phpbb\db\driver;
15
16/**
17* PostgreSQL Database Abstraction Layer
18* Minimum Requirement is Version 8.3+
19*/
20class 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}