Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 188
0.00% covered (danger)
0.00%
0 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
Oracle
0.00% covered (danger)
0.00%
0 / 188
0.00% covered (danger)
0.00%
0 / 22
3660
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 getHandle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __destruct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 prepare
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 prepRowId
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getDataTypeConstant
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
110
 bindParam
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 returnParam
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 exec
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 commit
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 rollback
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 free
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 simpleSelect
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 simpleDelete
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
20
 simpleInsert
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
42
 simpleSql
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 clearError
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 handleError
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getLastError
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLastErrorType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLastSql
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHtmlError
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
72
1<?php
2
3/**
4 * Oracle support code for VTLS Virtua Driver
5 *
6 * PHP version 8
7 *
8 * Copyright (C) University of Southern Queensland 2008.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  Oracle
25 * @author   Greg Pendlebury <vufind-tech@lists.sourceforge.net>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org/wiki/development Wiki
28 */
29
30namespace VuFind\Connection;
31
32use function count;
33use function is_array;
34
35/**
36 * Oracle support code for VTLS Virtua Driver
37 *
38 * @category VuFind
39 * @package  Oracle
40 * @author   Greg Pendlebury <vufind-tech@lists.sourceforge.net>
41 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
42 * @link     https://vufind.org/wiki/development Wiki
43 */
44class Oracle
45{
46    /**
47     * Database Handle
48     *
49     * @var resource
50     */
51    protected $dbHandle;
52
53    /**
54     * Error information - message
55     *
56     * @var string
57     */
58    protected $lastError;
59
60    /**
61     * Error information - type
62     *
63     * @var string
64     */
65    protected $lastErrorType;
66
67    /**
68     * Error information - bind params
69     *
70     * @var array
71     */
72    protected $lastErrorFields;
73
74    /**
75     * Error information - SQL attempted
76     *
77     * @var string
78     */
79    protected $lastSql;
80
81    /**
82     * Constructor -- connect to database.
83     *
84     * @param string $username Username for connection
85     * @param string $password Password for connection
86     * @param string $tns      TNS specification for connection
87     */
88    public function __construct($username, $password, $tns)
89    {
90        $this->clearError();
91        $tmp = error_reporting(1);
92        if ($this->dbHandle = @oci_connect($username, $password, $tns)) {
93            error_reporting($tmp);
94        } else {
95            error_reporting($tmp);
96            $this->handleError('connect', oci_error());
97            throw new \Exception('Oracle connection problem.');
98        }
99    }
100
101    /**
102     * Get access to the Oracle handle.
103     *
104     * @return resource
105     */
106    public function getHandle()
107    {
108        return $this->dbHandle;
109    }
110
111    /**
112     * Destructor
113     *
114     * @return void
115     */
116    public function __destruct()
117    {
118        // Close the OCI connection unless we failed to establish it:
119        if ($this->dbHandle !== false) {
120            oci_close($this->dbHandle);
121        }
122    }
123
124    /**
125     * Wrapper around oci_parse.
126     *
127     * @param string $sql SQL statement to prepare.
128     *
129     * @return mixed      SQL resource on success, boolean false otherwise.
130     */
131    public function prepare($sql)
132    {
133        if ($parsed = @oci_parse($this->dbHandle, $sql)) {
134            return $parsed;
135        } else {
136            $this->handleError('parsing', oci_error($this->dbHandle), $sql);
137            return false;
138        }
139    }
140
141    /**
142     * Wrapper around oci_new_descriptor.
143     *
144     * @return mixed New descriptor on success, boolean false otherwise.
145     */
146    public function prepRowId()
147    {
148        if ($new_id = @oci_new_descriptor($this->dbHandle, OCI_D_ROWID)) {
149            return $new_id;
150        } else {
151            $this->handleError('new_descriptor', oci_error($this->dbHandle));
152            return false;
153        }
154    }
155
156    /**
157     * Convert data type name into constant
158     *
159     * @param string $data_type Data type (string, integer, float, long, date,
160     * row_id, clob, or blob)
161     *
162     * @return int
163     */
164    protected function getDataTypeConstant($data_type)
165    {
166        switch ($data_type) {
167            case 'integer':
168                return SQLT_INT;
169            case 'float':
170                return SQLT_FLT;
171            case 'long':
172                return SQLT_LNG;
173            case 'row_id':
174                return SQLT_RDD;
175            case 'clob':
176                return SQLT_CLOB;
177            case 'blob':
178                return SQLT_BLOB;
179            case 'string':
180            case 'date':
181            default:
182                // Date and string are redundant since default is varchar,
183                //  but they're here for clarity.
184                return SQLT_CHR;
185        }
186    }
187
188    /**
189     * Wrapper around oci_bind_by_name.
190     *
191     * @param resource $parsed       Result returned by prepare() method.
192     * @param string   $place_holder The colon-prefixed bind variable placeholder
193     * used in the statement.
194     * @param string   $data         The PHP variable to be associated with
195     * $place_holder
196     * @param string   $data_type    The type of $data (string, integer, float,
197     * long, date, row_id, clob, or blob)
198     * @param int      $length       Sets the maximum length for the data. If you
199     * set it to -1, this function will use the current length of variable to set
200     * the maximum length.
201     *
202     * @return bool
203     */
204    public function bindParam(
205        $parsed,
206        $place_holder,
207        $data,
208        $data_type = 'string',
209        $length = -1
210    ) {
211        $success = @oci_bind_by_name(
212            $parsed,
213            $place_holder,
214            $data,
215            $length,
216            $this->getDataTypeConstant($data_type)
217        );
218        if ($success) {
219            return true;
220        } else {
221            $this->handleError('binding', oci_error());
222            return false;
223        }
224    }
225
226    /**
227     * Same as bindParam(), but variable is parsed by reference to allow for correct
228     * functioning of the 'RETURNING' sql statement. Annoying, but putting it in two
229     * separate functions allows the user to pass string literals into bindParam
230     * without a fatal error.
231     *
232     * @param resource $parsed       Result returned by prepare() method.
233     * @param string   $place_holder The colon-prefixed bind variable placeholder
234     * used in the statement.
235     * @param string   $data         The PHP variable to be associated with
236     * $place_holder
237     * @param string   $data_type    The type of $data (string, integer, float,
238     * long, date, row_id, clob, or blob)
239     * @param int      $length       Sets the maximum length for the data. If you
240     * set it to -1, this function will use the current length of variable to set
241     * the maximum length.
242     *
243     * @return bool
244     */
245    public function returnParam(
246        $parsed,
247        $place_holder,
248        &$data,
249        $data_type = 'string',
250        $length = -1
251    ) {
252        $success = @oci_bind_by_name(
253            $parsed,
254            $place_holder,
255            $data,
256            $length,
257            $this->getDataTypeConstant($data_type)
258        );
259        if ($success) {
260            return true;
261        } else {
262            $this->handleError('binding', oci_error());
263            return false;
264        }
265    }
266
267    /**
268     * Wrapper around oci_execute.
269     *
270     * @param resource $parsed Result returned by prepare() method.
271     *
272     * @return bool
273     */
274    public function exec($parsed)
275    {
276        // OCI_DEFAULT == DO NOT COMMIT!!!
277        if (@oci_execute($parsed, OCI_DEFAULT)) {
278            return true;
279        } else {
280            $this->handleError('executing', oci_error($parsed));
281            return false;
282        }
283    }
284
285    /**
286     * Wrapper around oci_commit.
287     *
288     * @return bool
289     */
290    public function commit()
291    {
292        if (@oci_commit($this->dbHandle)) {
293            return true;
294        } else {
295            $this->handleError('commit', oci_error($this->dbHandle));
296            return false;
297        }
298    }
299
300    /**
301     * Wrapper around oci_rollback.
302     *
303     * @return bool
304     */
305    public function rollback()
306    {
307        if (@oci_rollback($this->dbHandle)) {
308            return true;
309        } else {
310            $this->handleError('rollback', oci_error($this->dbHandle));
311            return false;
312        }
313    }
314
315    /**
316     * Wrapper around oci_free_statement.
317     *
318     * @param resource $parsed Result returned by prepare() method.
319     *
320     * @return bool
321     */
322    public function free($parsed)
323    {
324        if (@oci_free_statement($parsed)) {
325            return true;
326        } else {
327            $this->handleError('free', oci_error($this->dbHandle));
328            return false;
329        }
330    }
331
332    /**
333     * Execute a SQL statement and return the results.
334     *
335     * @param string $sql    SQL to execute
336     * @param array  $fields Bind parameters (optional)
337     *
338     * @return array|bool    Results on success, false on error.
339     */
340    public function simpleSelect($sql, $fields = [])
341    {
342        $stmt = $this->prepare($sql);
343        foreach ($fields as $field => $datum) {
344            [$column, $type] = explode(':', $field);
345            $this->bindParam($stmt, ':' . $column, $datum, $type);
346        }
347
348        if ($this->exec($stmt)) {
349            oci_fetch_all($stmt, $return_array, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
350            $this->free($stmt);
351            return $return_array;
352        } else {
353            $this->lastErrorFields = $fields;
354            $this->free($stmt);
355            return false;
356        }
357    }
358
359    /**
360     * Delete row(s) from a table.
361     *
362     * @param string $table  Table to update.
363     * @param array  $fields Fields to use to match rows to delete.
364     *
365     * @return bool
366     */
367    public function simpleDelete($table, $fields = [])
368    {
369        $types   = [];
370        $data    = [];
371        $clauses = [];
372
373        // Split all the fields up into arrays
374        foreach ($fields as $field => $datum) {
375            [$column, $type] = explode(':', $field);
376            $types[$column] = $type;
377            $data[$column]  = $datum;
378            $clauses[]      = "$column = :$column";
379        }
380
381        // Prepare the SQL for child table - turn the columns in placeholders for
382        // the bind
383        $sql  = "DELETE FROM $table WHERE " . implode(' AND ', $clauses);
384        $delete = $this->prepare($sql);
385
386        // Bind Variables
387        foreach (array_keys($data) as $column) {
388            $this->bindParam(
389                $delete,
390                ':' . $column,
391                $data[$column],
392                $types[$column]
393            );
394        }
395
396        // Execute
397        if ($this->exec($delete)) {
398            $this->commit();
399            $this->free($delete);
400            return true;
401        } else {
402            $this->lastErrorFields = $fields;
403            $this->free($delete);
404            return false;
405        }
406    }
407
408    /**
409     * Insert a row into a table.
410     *
411     * @param string $table  Table to append to.
412     * @param array  $fields Data to write to table.
413     *
414     * @return bool
415     */
416    public function simpleInsert($table, $fields = [])
417    {
418        $types   = [];
419        $data    = [];
420        $columns = [];
421        $values  = [];
422
423        // Split all the fields up into arrays
424        foreach ($fields as $field => $datum) {
425            $tmp = explode(':', $field);
426            $column = array_shift($tmp);
427
428            // For binding
429            $types[$column] = array_shift($tmp);
430            $data[$column]  = $datum;
431
432            // For building the sql
433            $columns[]      = $column;
434            // Dates are special
435            if (count($tmp) > 0 && null !== $datum) {
436                $values[] = "TO_DATE(:$column, '" . implode(':', $tmp) . "')";
437            } else {
438                $values[] = ":$column";
439            }
440        }
441
442        $sql  = "INSERT INTO $table (" . implode(', ', $columns) . ') VALUES (' .
443            implode(', ', $values) . ')';
444        $insert = $this->prepare($sql);
445
446        // Bind Variables
447        foreach (array_keys($data) as $column) {
448            $this->bindParam(
449                $insert,
450                ':' . $column,
451                $data[$column],
452                $types[$column]
453            );
454        }
455
456        // Execute
457        if ($this->exec($insert)) {
458            $this->commit();
459            $this->free($insert);
460            return true;
461        } else {
462            $this->lastErrorFields = $fields;
463            $this->free($insert);
464            return false;
465        }
466    }
467
468    /**
469     * Execute a simple SQL statement.
470     *
471     * @param string $sql    SQL to execute
472     * @param array  $fields Bind parameters (optional)
473     *
474     * @return bool
475     */
476    public function simpleSql($sql, $fields = [])
477    {
478        $stmt = $this->prepare($sql);
479        foreach ($fields as $field => $datum) {
480            [$column, $type] = explode(':', $field);
481            $this->bindParam($stmt, ':' . $column, $datum, $type);
482        }
483        if ($this->exec($stmt)) {
484            $this->commit();
485            $this->free($stmt);
486            return true;
487        } else {
488            $this->lastErrorFields = $fields;
489            $this->free($stmt);
490            return false;
491        }
492    }
493
494    /**
495     * Clear out internal error tracking details.
496     *
497     * @return void
498     */
499    protected function clearError()
500    {
501        $this->lastError       = null;
502        $this->lastErrorType   = null;
503        $this->lastErrorFields = null;
504        $this->lastSql         = null;
505    }
506
507    /**
508     * Store information about an error.
509     *
510     * @param string $type  Type of error
511     * @param string $error Detailed error message
512     * @param string $sql   SQL statement that caused error
513     *
514     * @return void
515     */
516    protected function handleError($type, $error, $sql = '')
517    {
518        // All we are doing at the moment is storing it
519        $this->lastError       = $error;
520        $this->lastErrorType   = $type;
521        $this->lastSql         = $sql;
522    }
523
524    /**
525     * Error Retrieval -- last error message.
526     *
527     * @return string
528     */
529    public function getLastError()
530    {
531        return $this->lastError;
532    }
533
534    /**
535     * Error Retrieval -- last error type.
536     *
537     * @return string
538     */
539    public function getLastErrorType()
540    {
541        return $this->lastErrorType;
542    }
543
544    /**
545     * Error Retrieval -- SQL that triggered last error.
546     *
547     * @return string
548     */
549    public function getLastSql()
550    {
551        return $this->lastSql;
552    }
553
554    /**
555     * Error Retrieval -- full details formatted as HTML.
556     *
557     * @return string
558     */
559    public function getHtmlError()
560    {
561        if ($this->lastError == null) {
562            return 'No error found!';
563        }
564
565        // Generic stuff
566        $output  = "<b>ORACLE ERROR</b><br/>\n";
567        $output .= "Oracle '" . $this->lastErrorType . "' Error<br />\n";
568        $output .= "=============<br />\n";
569        foreach ($this->lastError as $key => $value) {
570            $output .= "($key) => $value<br />\n";
571        }
572
573        // Anything special for this error type?
574        switch ($this->lastErrorType) {
575            case 'parsing':
576                $output .= "=============<br />\n";
577                $output .= "Offset into SQL:<br />\n";
578                $output .=
579                    substr($this->lastError['sqltext'], $this->lastError['offset']) .
580                    "\n";
581                break;
582            case 'executing':
583                $output .= "=============<br />\n";
584                $output .= "Offset into SQL:<br />\n";
585                $output .=
586                    substr($this->lastError['sqltext'], $this->lastError['offset']) .
587                    "<br />\n";
588                if (count($this->lastErrorFields) > 0) {
589                    $output .= "=============<br />\n";
590                    $output .= "Bind Variables:<br />\n";
591                    foreach ($this->lastErrorFields as $k => $l) {
592                        if (is_array($l)) {
593                            $output .= "$k => (" . implode(', ', $l) . ")<br />\n";
594                        } else {
595                            $output .= "$k => $l<br />\n";
596                        }
597                    }
598                }
599                break;
600        }
601
602        $this->clearError();
603        return $output;
604    }
605}