Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 274
0.00% covered (danger)
0.00%
0 / 21
CRAP
0.00% covered (danger)
0.00%
0 / 1
oracle
0.00% covered (danger)
0.00%
0 / 274
0.00% covered (danger)
0.00%
0 / 21
16002
0.00% covered (danger)
0.00%
0 / 1
 sql_connect
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
156
 sql_server_info
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 _sql_transaction
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 _rewrite_col_compare
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 _rewrite_where
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
210
 sql_query
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 1
1332
 _sql_query_limit
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 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 / 18
0.00% covered (danger)
0.00%
0 / 1
90
 sql_rowseek
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
56
 sql_last_inserted_id
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
56
 sql_freeresult
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 sql_escape
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 _sql_like_expression
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 _sql_not_like_expression
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 _sql_bit_and
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 _sql_bit_or
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 _sql_error
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 _sql_close
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 _sql_report
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 1
90
 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* Oracle Database Abstraction Layer
18*/
19class oracle extends \phpbb\db\driver\driver
20{
21    var $connect_error = '';
22
23    /** @var array|false Last error result or false if no last error set */
24    private $last_error_result = false;
25
26    /**
27    * {@inheritDoc}
28    */
29    function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false)
30    {
31        $this->persistency = $persistency;
32        $this->user = $sqluser;
33        $this->server = $sqlserver . (($port) ? ':' . $port : '');
34        $this->dbname = $database;
35
36        $connect = $database;
37
38        // support for "easy connect naming"
39        if ($sqlserver !== '' && $sqlserver !== '/')
40        {
41            if (substr($sqlserver, -1, 1) == '/')
42            {
43                $sqlserver == substr($sqlserver, 0, -1);
44            }
45            $connect = $sqlserver . (($port) ? ':' . $port : '') . '/' . $database;
46        }
47
48        if ($new_link)
49        {
50            if (!function_exists('oci_new_connect'))
51            {
52                $this->connect_error = 'oci_new_connect function does not exist, is oci extension installed?';
53                return $this->sql_error('');
54            }
55            $this->db_connect_id = @oci_new_connect($this->user, $sqlpassword, $connect, 'UTF8');
56        }
57        else if ($this->persistency)
58        {
59            if (!function_exists('oci_pconnect'))
60            {
61                $this->connect_error = 'oci_pconnect function does not exist, is oci extension installed?';
62                return $this->sql_error('');
63            }
64            $this->db_connect_id = @oci_pconnect($this->user, $sqlpassword, $connect, 'UTF8');
65        }
66        else
67        {
68            if (!function_exists('oci_connect'))
69            {
70                $this->connect_error = 'oci_connect function does not exist, is oci extension installed?';
71                return $this->sql_error('');
72            }
73            $this->db_connect_id = @oci_connect($this->user, $sqlpassword, $connect, 'UTF8');
74        }
75
76        return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error('');
77    }
78
79    /**
80    * {@inheritDoc}
81    */
82    function sql_server_info($raw = false, $use_cache = true)
83    {
84        /**
85        * force $use_cache false.  I didn't research why the caching code below is commented out
86        * but I assume its because the Oracle extension provides a direct method to access it
87        * without a query.
88        */
89/*
90        global $cache;
91
92        if (empty($cache) || ($this->sql_server_version = $cache->get('oracle_version')) === false)
93        {
94            $result = @ociparse($this->db_connect_id, 'SELECT * FROM v$version WHERE banner LIKE \'Oracle%\'');
95            @ociexecute($result, OCI_DEFAULT);
96            @ocicommit($this->db_connect_id);
97
98            $row = array();
99            @ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS);
100            @ocifreestatement($result);
101            $this->sql_server_version = trim($row['BANNER']);
102
103            $cache->put('oracle_version', $this->sql_server_version);
104        }
105*/
106        $this->sql_server_version = @oci_server_version($this->db_connect_id);
107
108        return $this->sql_server_version;
109    }
110
111    /**
112    * {@inheritDoc}
113    */
114    protected function _sql_transaction(string $status = 'begin'): bool
115    {
116        switch ($status)
117        {
118            case 'begin':
119                return true;
120
121            case 'commit':
122                return @oci_commit($this->db_connect_id);
123
124            case 'rollback':
125                return @oci_rollback($this->db_connect_id);
126        }
127
128        return true;
129    }
130
131    /**
132    * Oracle specific code to handle the fact that it does not compare columns properly
133    * @access private
134    */
135    function _rewrite_col_compare($args)
136    {
137        if (count($args) == 4)
138        {
139            if ($args[2] == '=')
140            {
141                return '(' . $args[0] . ' OR (' . $args[1] . ' is NULL AND ' . $args[3] . ' is NULL))';
142            }
143            else if ($args[2] == '<>')
144            {
145                // really just a fancy way of saying foo <> bar or (foo is NULL XOR bar is NULL) but SQL has no XOR :P
146                return '(' . $args[0] . ' OR ((' . $args[1] . ' is NULL AND ' . $args[3] . ' is NOT NULL) OR (' . $args[1] . ' is NOT NULL AND ' . $args[3] . ' is NULL)))';
147            }
148        }
149        else
150        {
151            return $this->_rewrite_where($args[0]);
152        }
153    }
154
155    /**
156    * Oracle specific code to handle it's lack of sanity
157    * @access private
158    */
159    function _rewrite_where($where_clause)
160    {
161        preg_match_all('/\s*(AND|OR)?\s*([\w_.()]++)\s*(?:(=|<[=>]?|>=?|LIKE)\s*((?>\'(?>[^\']++|\'\')*+\'|[\d\-.()]+))|((NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d\-.]+,? ?)*+\)))/', $where_clause, $result, PREG_SET_ORDER);
162        $out = '';
163        foreach ($result as $val)
164        {
165            if (!isset($val[5]))
166            {
167                if ($val[4] !== "''")
168                {
169                    $out .= $val[0];
170                }
171                else
172                {
173                    $out .= ' ' . $val[1] . ' ' . $val[2];
174                    if ($val[3] == '=')
175                    {
176                        $out .= ' is NULL';
177                    }
178                    else if ($val[3] == '<>')
179                    {
180                        $out .= ' is NOT NULL';
181                    }
182                }
183            }
184            else
185            {
186                $in_clause = array();
187                $sub_exp = substr($val[5], strpos($val[5], '(') + 1, -1);
188                $extra = false;
189                preg_match_all('/\'(?>[^\']++|\'\')*+\'|[\d\-.]++/', $sub_exp, $sub_vals, PREG_PATTERN_ORDER);
190                $i = 0;
191                foreach ($sub_vals[0] as $sub_val)
192                {
193                    // two things:
194                    // 1) This determines if an empty string was in the IN clausing, making us turn it into a NULL comparison
195                    // 2) This fixes the 1000 list limit that Oracle has (ORA-01795)
196                    if ($sub_val !== "''")
197                    {
198                        $in_clause[(int) $i++/1000][] = $sub_val;
199                    }
200                    else
201                    {
202                        $extra = true;
203                    }
204                }
205                if (!$extra && $i < 1000)
206                {
207                    $out .= $val[0];
208                }
209                else
210                {
211                    $out .= ' ' . $val[1] . '(';
212                    $in_array = array();
213
214                    // constuct each IN() clause
215                    foreach ($in_clause as $in_values)
216                    {
217                        $in_array[] = $val[2] . ' ' . (isset($val[6]) ? $val[6] : '') . 'IN(' . implode(', ', $in_values) . ')';
218                    }
219
220                    // Join the IN() clauses against a few ORs (IN is just a nicer OR anyway)
221                    $out .= implode(' OR ', $in_array);
222
223                    // handle the empty string case
224                    if ($extra)
225                    {
226                        $out .= ' OR ' . $val[2] . ' is ' . (isset($val[6]) ? $val[6] : '') . 'NULL';
227                    }
228                    $out .= ')';
229
230                    unset($in_array, $in_clause);
231                }
232            }
233        }
234
235        return $out;
236    }
237
238    /**
239    * {@inheritDoc}
240    */
241    function sql_query($query = '', $cache_ttl = 0)
242    {
243        if ($query != '')
244        {
245            global $cache;
246
247            if ($this->debug_sql_explain)
248            {
249                $this->sql_report('start', $query);
250            }
251            else if ($this->debug_load_time)
252            {
253                $this->curtime = microtime(true);
254            }
255
256            $this->last_query_text = $query;
257            $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
258            $this->sql_add_num_queries($this->query_result);
259
260            if ($this->query_result === false)
261            {
262                $in_transaction = false;
263                if (!$this->transaction)
264                {
265                    $this->sql_transaction('begin');
266                }
267                else
268                {
269                    $in_transaction = true;
270                }
271
272                $array = array();
273
274                // We overcome Oracle's 4000 char limit by binding vars
275                if (strlen($query) > 4000)
276                {
277                    if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/sU', $query, $regs))
278                    {
279                        if (strlen($regs[3]) > 4000)
280                        {
281                            $cols = explode(', ', $regs[2]);
282
283                            preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d\-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER);
284
285/*                        The code inside this comment block breaks clob handling, but does allow the
286                        database restore script to work.  If you want to allow no posts longer than 4KB
287                        and/or need the db restore script, uncomment this.
288
289
290                            if (count($cols) !== count($vals))
291                            {
292                                // Try to replace some common data we know is from our restore script or from other sources
293                                $regs[3] = str_replace("'||chr(47)||'", '/', $regs[3]);
294                                $_vals = explode(', ', $regs[3]);
295
296                                $vals = array();
297                                $is_in_val = false;
298                                $i = 0;
299                                $string = '';
300
301                                foreach ($_vals as $value)
302                                {
303                                    if (strpos($value, "'") === false && !$is_in_val)
304                                    {
305                                        $vals[$i++] = $value;
306                                        continue;
307                                    }
308
309                                    if (substr($value, -1) === "'")
310                                    {
311                                        $vals[$i] = $string . (($is_in_val) ? ', ' : '') . $value;
312                                        $string = '';
313                                        $is_in_val = false;
314
315                                        if ($vals[$i][0] !== "'")
316                                        {
317                                            $vals[$i] = "''" . $vals[$i];
318                                        }
319                                        $i++;
320                                        continue;
321                                    }
322                                    else
323                                    {
324                                        $string .= (($is_in_val) ? ', ' : '') . $value;
325                                        $is_in_val = true;
326                                    }
327                                }
328
329                                if ($string)
330                                {
331                                    // New value if cols != value
332                                    $vals[(count($cols) !== count($vals)) ? $i : $i - 1] .= $string;
333                                }
334
335                                $vals = array(0 => $vals);
336                            }
337*/
338
339                            $inserts = $vals[0];
340                            unset($vals);
341
342                            foreach ($inserts as $key => $value)
343                            {
344                                if (!empty($value) && $value[0] === "'" && strlen($value) > 4002) // check to see if this thing is greater than the max + 'x2
345                                {
346                                    $inserts[$key] = ':' . strtoupper($cols[$key]);
347                                    $array[$inserts[$key]] = str_replace("''", "'", substr($value, 1, -1));
348                                }
349                            }
350
351                            $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')';
352                        }
353                    }
354                    else if (preg_match_all('/^(UPDATE [\\w_]++\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d\-.]+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d\-.]+))*+)\\s+(WHERE.*)$/s', $query, $data, PREG_SET_ORDER))
355                    {
356                        if (strlen($data[0][2]) > 4000)
357                        {
358                            $update = $data[0][1];
359                            $where = $data[0][3];
360                            preg_match_all('/([\\w_]++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d\-.]++)/', $data[0][2], $temp, PREG_SET_ORDER);
361                            unset($data);
362
363                            $cols = array();
364                            foreach ($temp as $value)
365                            {
366                                if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 4002) // check to see if this thing is greater than the max + 'x2
367                                {
368                                    $cols[] = $value[1] . '=:' . strtoupper($value[1]);
369                                    $array[$value[1]] = str_replace("''", "'", substr($value[2], 1, -1));
370                                }
371                                else
372                                {
373                                    $cols[] = $value[1] . '=' . $value[2];
374                                }
375                            }
376
377                            $query = $update . implode(', ', $cols) . ' ' . $where;
378                            unset($cols);
379                        }
380                    }
381                }
382
383                switch (substr($query, 0, 6))
384                {
385                    case 'DELETE':
386                        if (preg_match('/^(DELETE FROM [\w_]++ WHERE)((?:\s*(?:AND|OR)?\s*[\w_]+\s*(?:(?:=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d\-.]+)|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d\-.]+,? ?)*+\)))*+)$/', $query, $regs))
387                        {
388                            $query = $regs[1] . $this->_rewrite_where($regs[2]);
389                            unset($regs);
390                        }
391                    break;
392
393                    case 'UPDATE':
394                        if (preg_match('/^(UPDATE [\\w_]++\\s+SET [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d\-.]++|:\w++)(?:, [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d\-.]++|:\w++))*+\\s+WHERE)(.*)$/s',  $query, $regs))
395                        {
396                            $query = $regs[1] . $this->_rewrite_where($regs[2]);
397                            unset($regs);
398                        }
399                    break;
400
401                    case 'SELECT':
402                        $query = preg_replace_callback('/([\w_.]++)\s*(?:(=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d\-.]++|([\w_.]++))|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d\-.]++,? ?)*+\))/', array($this, '_rewrite_col_compare'), $query);
403                    break;
404                }
405
406                $this->query_result = @oci_parse($this->db_connect_id, $query);
407
408                foreach ($array as $key => $value)
409                {
410                    @oci_bind_by_name($this->query_result, $key, $array[$key], -1);
411                }
412
413                $success = @oci_execute($this->query_result, OCI_DEFAULT);
414
415                if (!$success)
416                {
417                    $this->sql_error($query);
418                    $this->query_result = false;
419                }
420                else
421                {
422                    if (!$in_transaction)
423                    {
424                        $this->sql_transaction('commit');
425                    }
426                }
427
428                if ($this->debug_sql_explain)
429                {
430                    $this->sql_report('stop', $query);
431                }
432                else if ($this->debug_load_time)
433                {
434                    $this->sql_time += microtime(true) - $this->curtime;
435                }
436
437                if (!$this->query_result)
438                {
439                    return false;
440                }
441
442                $safe_query_id = $this->clean_query_id($this->query_result);
443
444                if ($cache && $cache_ttl)
445                {
446                    $this->open_queries[$safe_query_id] = $this->query_result;
447                    $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
448                }
449                else if (strpos($query, 'SELECT') === 0)
450                {
451                    $this->open_queries[$safe_query_id] = $this->query_result;
452                }
453            }
454            else if ($this->debug_sql_explain)
455            {
456                $this->sql_report('fromcache', $query);
457            }
458        }
459        else
460        {
461            return false;
462        }
463
464        return $this->query_result;
465    }
466
467    /**
468    * {@inheritDoc}
469    */
470    protected function _sql_query_limit(string $query, int $total, int $offset = 0, int $cache_ttl = 0)
471    {
472        $this->query_result = false;
473
474        $query = 'SELECT * FROM (SELECT /*+ FIRST_ROWS */ rownum AS xrownum, a.* FROM (' . $query . ') a WHERE rownum <= ' . ($offset + $total) . ') WHERE xrownum >= ' . $offset;
475
476        return $this->sql_query($query, $cache_ttl);
477    }
478
479    /**
480    * {@inheritDoc}
481    */
482    function sql_affectedrows()
483    {
484        return ($this->query_result) ? @oci_num_rows($this->query_result) : false;
485    }
486
487    /**
488    * {@inheritDoc}
489    */
490    function sql_fetchrow($query_id = false)
491    {
492        global $cache;
493
494        if ($query_id === false)
495        {
496            $query_id = $this->query_result;
497        }
498
499        $safe_query_id = $this->clean_query_id($query_id);
500        if ($cache && $cache->sql_exists($safe_query_id))
501        {
502            return $cache->sql_fetchrow($safe_query_id);
503        }
504
505        if ($query_id)
506        {
507            if (!$row = oci_fetch_array($query_id, OCI_ASSOC + OCI_RETURN_NULLS))
508            {
509                return false;
510            }
511
512            $result_row = array();
513            foreach ($row as $key => $value)
514            {
515                // Oracle treats empty strings as null
516                if (is_null($value))
517                {
518                    $value = '';
519                }
520
521                // OCI->CLOB?
522                if (is_object($value))
523                {
524                    $value = $value->load();
525                }
526
527                $result_row[strtolower($key)] = $value;
528            }
529
530            return $result_row;
531        }
532
533        return false;
534    }
535
536    /**
537    * {@inheritDoc}
538    */
539    function sql_rowseek($rownum, &$query_id)
540    {
541        global $cache;
542
543        if ($query_id === false)
544        {
545            $query_id = $this->query_result;
546        }
547
548        $safe_query_id = $this->clean_query_id($query_id);
549        if ($cache && $cache->sql_exists($safe_query_id))
550        {
551            return $cache->sql_rowseek($rownum, $safe_query_id);
552        }
553
554        if (!$query_id)
555        {
556            return false;
557        }
558
559        // Reset internal pointer
560        @oci_execute($query_id, OCI_DEFAULT);
561
562        // We do not fetch the row for rownum == 0 because then the next resultset would be the second row
563        for ($i = 0; $i < $rownum; $i++)
564        {
565            if (!$this->sql_fetchrow($query_id))
566            {
567                return false;
568            }
569        }
570
571        return true;
572    }
573
574    /**
575     * {@inheritdoc}
576     */
577    public function sql_last_inserted_id()
578    {
579        $query_id = $this->query_result;
580
581        if ($query_id !== false && $this->last_query_text != '')
582        {
583            if (preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#is', $this->last_query_text, $tablename))
584            {
585                $query = 'SELECT ' . $tablename[1] . '_seq.currval FROM DUAL';
586                $stmt = @oci_parse($this->db_connect_id, $query);
587                if ($stmt)
588                {
589                    $success = @oci_execute($stmt, OCI_DEFAULT);
590
591                    if ($success)
592                    {
593                        $temp_array = oci_fetch_array($stmt, OCI_ASSOC + OCI_RETURN_NULLS);
594                        oci_free_statement($stmt);
595
596                        if (!empty($temp_array))
597                        {
598                            return $temp_array['CURRVAL'];
599                        }
600                        else
601                        {
602                            return false;
603                        }
604                    }
605                }
606            }
607        }
608
609        return false;
610    }
611
612    /**
613    * {@inheritDoc}
614    */
615    function sql_freeresult($query_id = false)
616    {
617        global $cache;
618
619        if ($query_id === false)
620        {
621            $query_id = $this->query_result;
622        }
623
624        $safe_query_id = $this->clean_query_id($query_id);
625        if ($cache && $cache->sql_exists($safe_query_id))
626        {
627             $cache->sql_freeresult($safe_query_id);
628        }
629        else if (isset($this->open_queries[$safe_query_id]))
630        {
631            unset($this->open_queries[$safe_query_id]);
632            oci_free_statement($query_id);
633        }
634    }
635
636    /**
637    * {@inheritDoc}
638    */
639    function sql_escape($msg)
640    {
641        return str_replace(array("'", "\0"), array("''", ''), $msg);
642    }
643
644    /**
645    * {@inheritDoc}
646    */
647    protected function _sql_like_expression(string $expression): string
648    {
649        return $expression . " ESCAPE '\\'";
650    }
651
652    /**
653    * {@inheritDoc}
654    */
655    protected function _sql_not_like_expression(string $expression): string
656    {
657        return $expression . " ESCAPE '\\'";
658    }
659
660    function _sql_bit_and($column_name, $bit, $compare = '')
661    {
662        return 'BITAND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : '');
663    }
664
665    function _sql_bit_or($column_name, $bit, $compare = '')
666    {
667        return 'BITOR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : '');
668    }
669
670    /**
671    * {@inheritDoc}
672    */
673    protected function _sql_error(): array
674    {
675        if (function_exists('oci_error'))
676        {
677            $error = @oci_error();
678            $error = (!$error) ? @oci_error($this->query_result) : $error;
679            $error = (!$error) ? @oci_error($this->db_connect_id) : $error;
680
681            if ($error)
682            {
683                $this->last_error_result = $error;
684            }
685            else
686            {
687                $error = $this->last_error_result ?: ['message' => '', 'code' => ''];
688            }
689        }
690        else
691        {
692            $error = array(
693                'message'    => $this->connect_error,
694                'code'        => '',
695            );
696        }
697
698        return $error;
699    }
700
701    /**
702     * {@inheritDoc}
703     */
704    protected function _sql_close(): bool
705    {
706        return @oci_close($this->db_connect_id);
707    }
708
709    /**
710    * {@inheritDoc}
711    */
712    protected function _sql_report(string $mode, string $query = ''): void
713    {
714        switch ($mode)
715        {
716            case 'start':
717
718                $html_table = false;
719
720                // Grab a plan table, any will do
721                $sql = "SELECT table_name
722                    FROM USER_TABLES
723                    WHERE table_name LIKE '%PLAN_TABLE%'";
724                $stmt = oci_parse($this->db_connect_id, $sql);
725                oci_execute($stmt);
726
727                if ($result = oci_fetch_array($stmt, OCI_ASSOC + OCI_RETURN_NULLS))
728                {
729                    $table = $result['TABLE_NAME'];
730
731                    // This is the statement_id that will allow us to track the plan
732                    $statement_id = substr(md5($query), 0, 30);
733
734                    // Remove any stale plans
735                    $stmt2 = oci_parse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'");
736                    oci_execute($stmt2);
737                    oci_free_statement($stmt2);
738
739                    // Explain the plan
740                    $sql = "EXPLAIN PLAN
741                        SET STATEMENT_ID = '$statement_id'
742                        FOR $query";
743                    $stmt2 = oci_parse($this->db_connect_id, $sql);
744                    oci_execute($stmt2);
745                    oci_free_statement($stmt2);
746
747                    // Get the data from the plan
748                    $sql = "SELECT operation, options, object_name, object_type, cardinality, cost
749                        FROM plan_table
750                        START WITH id = 0 AND statement_id = '$statement_id'
751                        CONNECT BY PRIOR id = parent_id
752                            AND statement_id = '$statement_id'";
753                    $stmt2 = oci_parse($this->db_connect_id, $sql);
754                    oci_execute($stmt2);
755
756                    while ($row = oci_fetch_array($stmt2, OCI_ASSOC + OCI_RETURN_NULLS))
757                    {
758                        $html_table = $this->sql_report('add_select_row', $query, $html_table, $row);
759                    }
760
761                    oci_free_statement($stmt2);
762
763                    // Remove the plan we just made, we delete them on request anyway
764                    $stmt2 = oci_parse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'");
765                    oci_execute($stmt2);
766                    oci_free_statement($stmt2);
767                }
768
769                oci_free_statement($stmt);
770
771                if ($html_table)
772                {
773                    $this->html_hold .= '</table>';
774                }
775
776            break;
777
778            case 'fromcache':
779                $endtime = explode(' ', microtime());
780                $endtime = $endtime[0] + $endtime[1];
781
782                $result = @oci_parse($this->db_connect_id, $query);
783                if ($result)
784                {
785                    $success = @oci_execute($result, OCI_DEFAULT);
786                    if ($success)
787                    {
788                        while ($row = oci_fetch_array($result, OCI_ASSOC + OCI_RETURN_NULLS))
789                        {
790                            // Take the time spent on parsing rows into account
791                        }
792                        @oci_free_statement($result);
793                    }
794                }
795
796                $splittime = explode(' ', microtime());
797                $splittime = $splittime[0] + $splittime[1];
798
799                $this->sql_report('record_fromcache', $query, $endtime, $splittime);
800
801            break;
802        }
803    }
804
805    /**
806    * {@inheritDoc}
807    */
808    function sql_quote($msg)
809    {
810        return '"' . $msg . '"';
811    }
812}