]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/DB/oci8.php
extlib/DB/DataObject - Fix PHP 7.3 Warning switch continue -> break
[quix0rs-gnu-social.git] / extlib / DB / oci8.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6  * The PEAR DB driver for PHP's oci8 extension
7  * for interacting with Oracle databases
8  *
9  * PHP version 5
10  *
11  * LICENSE: This source file is subject to version 3.0 of the PHP license
12  * that is available through the world-wide-web at the following URI:
13  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
14  * the PHP License and are unable to obtain it through the web, please
15  * send a note to license@php.net so we can mail you a copy immediately.
16  *
17  * @category   Database
18  * @package    DB
19  * @author     James L. Pine <jlp@valinux.com>
20  * @author     Daniel Convissor <danielc@php.net>
21  * @copyright  1997-2007 The PHP Group
22  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
23  * @version    CVS: $Id$
24  * @link       http://pear.php.net/package/DB
25  */
26
27 /**
28  * Obtain the DB_common class so it can be extended from
29  */
30 require_once 'DB/common.php';
31
32 /**
33  * The methods PEAR DB uses to interact with PHP's oci8 extension
34  * for interacting with Oracle databases
35  *
36  * Definitely works with versions 8 and 9 of Oracle.
37  *
38  * These methods overload the ones declared in DB_common.
39  *
40  * Be aware...  OCIError() only appears to return anything when given a
41  * statement, so functions return the generic DB_ERROR instead of more
42  * useful errors that have to do with feedback from the database.
43  *
44  * @category   Database
45  * @package    DB
46  * @author     James L. Pine <jlp@valinux.com>
47  * @author     Daniel Convissor <danielc@php.net>
48  * @copyright  1997-2007 The PHP Group
49  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
50  * @version    Release: 1.9.2
51  * @link       http://pear.php.net/package/DB
52  */
53 class DB_oci8 extends DB_common
54 {
55     // {{{ properties
56
57     /**
58      * The DB driver type (mysql, oci8, odbc, etc.)
59      * @var string
60      */
61     public $phptype = 'oci8';
62
63     /**
64      * The database syntax variant to be used (db2, access, etc.), if any
65      * @var string
66      */
67     public $dbsyntax = 'oci8';
68
69     /**
70      * The capabilities of this DB implementation
71      *
72      * The 'new_link' element contains the PHP version that first provided
73      * new_link support for this DBMS.  Contains false if it's unsupported.
74      *
75      * Meaning of the 'limit' element:
76      *   + 'emulate' = emulate with fetch row by number
77      *   + 'alter'   = alter the query
78      *   + false     = skip rows
79      *
80      * @var array
81      */
82     public $features = array(
83         'limit'         => 'alter',
84         'new_link'      => '5.0.0',
85         'numrows'       => 'subquery',
86         'pconnect'      => true,
87         'prepare'       => true,
88         'ssl'           => false,
89         'transactions'  => true,
90     );
91
92     /**
93      * A mapping of native error codes to DB error codes
94      * @var array
95      */
96     public $errorcode_map = array(
97         1     => DB_ERROR_CONSTRAINT,
98         900   => DB_ERROR_SYNTAX,
99         904   => DB_ERROR_NOSUCHFIELD,
100         913   => DB_ERROR_VALUE_COUNT_ON_ROW,
101         921   => DB_ERROR_SYNTAX,
102         923   => DB_ERROR_SYNTAX,
103         942   => DB_ERROR_NOSUCHTABLE,
104         955   => DB_ERROR_ALREADY_EXISTS,
105         1400  => DB_ERROR_CONSTRAINT_NOT_NULL,
106         1401  => DB_ERROR_INVALID,
107         1407  => DB_ERROR_CONSTRAINT_NOT_NULL,
108         1418  => DB_ERROR_NOT_FOUND,
109         1476  => DB_ERROR_DIVZERO,
110         1722  => DB_ERROR_INVALID_NUMBER,
111         2289  => DB_ERROR_NOSUCHTABLE,
112         2291  => DB_ERROR_CONSTRAINT,
113         2292  => DB_ERROR_CONSTRAINT,
114         2449  => DB_ERROR_CONSTRAINT,
115         12899 => DB_ERROR_INVALID,
116     );
117
118     /**
119      * The raw database connection created by PHP
120      * @var resource
121      */
122     public $connection;
123
124     /**
125      * The DSN information for connecting to a database
126      * @var array
127      */
128     public $dsn = array();
129
130
131     /**
132      * Should data manipulation queries be committed automatically?
133      * @var bool
134      * @access private
135      */
136     public $autocommit = true;
137
138     /**
139      * Stores the $data passed to execute() in the oci8 driver
140      *
141      * Gets reset to array() when simpleQuery() is run.
142      *
143      * Needed in case user wants to call numRows() after prepare/execute
144      * was used.
145      *
146      * @var array
147      * @access private
148      */
149     public $_data = array();
150
151     /**
152      * The result or statement handle from the most recently executed query
153      * @var resource
154      */
155     public $last_stmt;
156
157     /**
158      * Is the given prepared statement a data manipulation query?
159      * @var array
160      * @access private
161      */
162     public $manip_query = array();
163
164     /**
165      * Store of prepared SQL queries.
166      * @var array
167      * @access private
168      */
169     public $_prepared_queries = array();
170
171
172     // }}}
173     // {{{ constructor
174
175     /**
176      * This constructor calls <kbd>parent::__construct()</kbd>
177      *
178      * @return void
179      */
180     public function __construct()
181     {
182         parent::__construct();
183     }
184
185     // }}}
186     // {{{ connect()
187
188     /**
189      * Connect to the database server, log in and open the database
190      *
191      * Don't call this method directly.  Use DB::connect() instead.
192      *
193      * If PHP is at version 5.0.0 or greater:
194      *   + Generally, oci_connect() or oci_pconnect() are used.
195      *   + But if the new_link DSN option is set to true, oci_new_connect()
196      *     is used.
197      *
198      * When using PHP version 4.x, OCILogon() or OCIPLogon() are used.
199      *
200      * PEAR DB's oci8 driver supports the following extra DSN options:
201      *   + charset       The character set to be used on the connection.
202      *                    Only used if PHP is at version 5.0.0 or greater
203      *                    and the Oracle server is at 9.2 or greater.
204      *                    Available since PEAR DB 1.7.0.
205      *   + new_link      If set to true, causes subsequent calls to
206      *                    connect() to return a new connection link
207      *                    instead of the existing one.  WARNING: this is
208      *                    not portable to other DBMS's.
209      *                    Available since PEAR DB 1.7.0.
210      *
211      * @param array $dsn         the data source name
212      * @param bool  $persistent  should the connection be persistent?
213      *
214      * @return int  DB_OK on success. A DB_Error object on failure.
215      */
216     public function connect($dsn, $persistent = false)
217     {
218         if (!PEAR::loadExtension('oci8')) {
219             return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
220         }
221
222         $this->dsn = $dsn;
223         if ($dsn['dbsyntax']) {
224             $this->dbsyntax = $dsn['dbsyntax'];
225         }
226
227         // Backwards compatibility with DB < 1.7.0
228         if (empty($dsn['database']) && !empty($dsn['hostspec'])) {
229             $db = $dsn['hostspec'];
230         } else {
231             $db = $dsn['database'];
232         }
233
234         if (function_exists('oci_connect')) {
235             if (isset($dsn['new_link'])
236                 && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) {
237                 $connect_function = 'oci_new_connect';
238             } else {
239                 $connect_function = $persistent ? 'oci_pconnect'
240                                     : 'oci_connect';
241             }
242             if (isset($this->dsn['port']) && $this->dsn['port']) {
243                 $db = '//'.$db.':'.$this->dsn['port'];
244             }
245
246             $char = empty($dsn['charset']) ? null : $dsn['charset'];
247             $this->connection = @$connect_function(
248                 $dsn['username'],
249                 $dsn['password'],
250                 $db,
251                 $char
252             );
253             $error = OCIError();
254             if (!empty($error) && $error['code'] == 12541) {
255                 // Couldn't find TNS listener.  Try direct connection.
256                 $this->connection = @$connect_function(
257                     $dsn['username'],
258                     $dsn['password'],
259                     null,
260                     $char
261                 );
262             }
263         } else {
264             $connect_function = $persistent ? 'OCIPLogon' : 'OCILogon';
265             if ($db) {
266                 $this->connection = @$connect_function(
267                     $dsn['username'],
268                     $dsn['password'],
269                     $db
270                 );
271             } elseif ($dsn['username'] || $dsn['password']) {
272                 $this->connection = @$connect_function(
273                     $dsn['username'],
274                     $dsn['password']
275                 );
276             }
277         }
278
279         if (!$this->connection) {
280             $error = OCIError();
281             $error = (is_array($error)) ? $error['message'] : null;
282             return $this->raiseError(
283                 DB_ERROR_CONNECT_FAILED,
284                 null,
285                 null,
286                 null,
287                 $error
288             );
289         }
290         return DB_OK;
291     }
292
293     // }}}
294     // {{{ disconnect()
295
296     /**
297      * Disconnects from the database server
298      *
299      * @return bool  TRUE on success, FALSE on failure
300      */
301     public function disconnect()
302     {
303         if (function_exists('oci_close')) {
304             $ret = @oci_close($this->connection);
305         } else {
306             $ret = @OCILogOff($this->connection);
307         }
308         $this->connection = null;
309         return $ret;
310     }
311
312     // }}}
313     // {{{ simpleQuery()
314
315     /**
316      * Sends a query to the database server
317      *
318      * To determine how many rows of a result set get buffered using
319      * ocisetprefetch(), see the "result_buffering" option in setOptions().
320      * This option was added in Release 1.7.0.
321      *
322      * @param string  the SQL query string
323      *
324      * @return mixed  + a PHP result resrouce for successful SELECT queries
325      *                + the DB_OK constant for other successful queries
326      *                + a DB_Error object on failure
327      */
328     public function simpleQuery($query)
329     {
330         $this->_data = array();
331         $this->last_parameters = array();
332         $this->last_query = $query;
333         $query = $this->modifyQuery($query);
334         $result = @OCIParse($this->connection, $query);
335         if (!$result) {
336             return $this->oci8RaiseError();
337         }
338         if ($this->autocommit) {
339             $success = @OCIExecute($result, OCI_COMMIT_ON_SUCCESS);
340         } else {
341             $success = @OCIExecute($result, OCI_DEFAULT);
342         }
343         if (!$success) {
344             return $this->oci8RaiseError($result);
345         }
346         $this->last_stmt = $result;
347         if ($this->_checkManip($query)) {
348             return DB_OK;
349         } else {
350             @ocisetprefetch($result, $this->options['result_buffering']);
351             return $result;
352         }
353     }
354
355     // }}}
356     // {{{ nextResult()
357
358     /**
359      * Move the internal oracle result pointer to the next available result
360      *
361      * @param a valid oci8 result resource
362      *
363      * @access public
364      *
365      * @return true if a result is available otherwise return false
366      */
367     public function nextResult($result)
368     {
369         return false;
370     }
371
372     // }}}
373     // {{{ fetchInto()
374
375     /**
376      * Places a row from the result set into the given array
377      *
378      * Formating of the array and the data therein are configurable.
379      * See DB_result::fetchInto() for more information.
380      *
381      * This method is not meant to be called directly.  Use
382      * DB_result::fetchInto() instead.  It can't be declared "protected"
383      * because DB_result is a separate object.
384      *
385      * @param resource $result    the query result resource
386      * @param array    $arr       the referenced array to put the data in
387      * @param int      $fetchmode how the resulting array should be indexed
388      * @param int      $rownum    the row number to fetch (0 = first row)
389      *
390      * @return mixed  DB_OK on success, NULL when the end of a result set is
391      *                 reached or on failure
392      *
393      * @see DB_result::fetchInto()
394      */
395     public function fetchInto($result, &$arr, $fetchmode, $rownum = null)
396     {
397         if ($rownum !== null) {
398             return $this->raiseError(DB_ERROR_NOT_CAPABLE);
399         }
400         if ($fetchmode & DB_FETCHMODE_ASSOC) {
401             $moredata = @OCIFetchInto($result, $arr, OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS);
402             if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE &&
403                 $moredata) {
404                 $arr = array_change_key_case($arr, CASE_LOWER);
405             }
406         } else {
407             $moredata = OCIFetchInto($result, $arr, OCI_RETURN_NULLS+OCI_RETURN_LOBS);
408         }
409         if (!$moredata) {
410             return null;
411         }
412         if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
413             $this->_rtrimArrayValues($arr);
414         }
415         if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
416             $this->_convertNullArrayValuesToEmpty($arr);
417         }
418         return DB_OK;
419     }
420
421     // }}}
422     // {{{ freeResult()
423
424     /**
425      * Deletes the result set and frees the memory occupied by the result set
426      *
427      * This method is not meant to be called directly.  Use
428      * DB_result::free() instead.  It can't be declared "protected"
429      * because DB_result is a separate object.
430      *
431      * @param resource $result  PHP's query result resource
432      *
433      * @return bool  TRUE on success, FALSE if $result is invalid
434      *
435      * @see DB_result::free()
436      */
437     public function freeResult($result)
438     {
439         return is_resource($result) ? OCIFreeStatement($result) : false;
440     }
441
442     /**
443      * Frees the internal resources associated with a prepared query
444      *
445      * @param resource $stmt           the prepared statement's resource
446      * @param bool     $free_resource  should the PHP resource be freed too?
447      *                                  Use false if you need to get data
448      *                                  from the result set later.
449      *
450      * @return bool  TRUE on success, FALSE if $result is invalid
451      *
452      * @see DB_oci8::prepare()
453      */
454     public function freePrepared($stmt, $free_resource = true)
455     {
456         if (!is_resource($stmt)) {
457             return false;
458         }
459         if ($free_resource) {
460             @ocifreestatement($stmt);
461         }
462         if (isset($this->prepare_types[(int)$stmt])) {
463             unset($this->prepare_types[(int)$stmt]);
464             unset($this->manip_query[(int)$stmt]);
465             unset($this->_prepared_queries[(int)$stmt]);
466         } else {
467             return false;
468         }
469         return true;
470     }
471
472     // }}}
473     // {{{ numRows()
474
475     /**
476      * Gets the number of rows in a result set
477      *
478      * Only works if the DB_PORTABILITY_NUMROWS portability option
479      * is turned on.
480      *
481      * This method is not meant to be called directly.  Use
482      * DB_result::numRows() instead.  It can't be declared "protected"
483      * because DB_result is a separate object.
484      *
485      * @param resource $result  PHP's query result resource
486      *
487      * @return int  the number of rows.  A DB_Error object on failure.
488      *
489      * @see DB_result::numRows(), DB_common::setOption()
490      */
491     public function numRows($result)
492     {
493         // emulate numRows for Oracle.  yuck.
494         if ($this->options['portability'] & DB_PORTABILITY_NUMROWS &&
495             $result === $this->last_stmt) {
496             $countquery = 'SELECT COUNT(*) FROM ('.$this->last_query.')';
497             $save_query = $this->last_query;
498             $save_stmt = $this->last_stmt;
499
500             $count = $this->query($countquery);
501
502             // Restore the last query and statement.
503             $this->last_query = $save_query;
504             $this->last_stmt = $save_stmt;
505             
506             if (DB::isError($count) ||
507                 DB::isError($row = $count->fetchRow(DB_FETCHMODE_ORDERED))) {
508                 return $this->raiseError(DB_ERROR_NOT_CAPABLE);
509             }
510
511             return $row[0];
512         }
513         return $this->raiseError(DB_ERROR_NOT_CAPABLE);
514     }
515
516     // }}}
517     // {{{ numCols()
518
519     /**
520      * Gets the number of columns in a result set
521      *
522      * This method is not meant to be called directly.  Use
523      * DB_result::numCols() instead.  It can't be declared "protected"
524      * because DB_result is a separate object.
525      *
526      * @param resource $result  PHP's query result resource
527      *
528      * @return int  the number of columns.  A DB_Error object on failure.
529      *
530      * @see DB_result::numCols()
531      */
532     public function numCols($result)
533     {
534         $cols = @OCINumCols($result);
535         if (!$cols) {
536             return $this->oci8RaiseError($result);
537         }
538         return $cols;
539     }
540
541     // }}}
542     // {{{ prepare()
543
544     /**
545      * Prepares a query for multiple execution with execute().
546      *
547      * With oci8, this is emulated.
548      *
549      * prepare() requires a generic query as string like <code>
550      *    INSERT INTO numbers VALUES (?, ?, ?)
551      * </code>.  The <kbd>?</kbd> characters are placeholders.
552      *
553      * Three types of placeholders can be used:
554      *   + <kbd>?</kbd>  a quoted scalar value, i.e. strings, integers
555      *   + <kbd>!</kbd>  value is inserted 'as is'
556      *   + <kbd>&</kbd>  requires a file name.  The file's contents get
557      *                     inserted into the query (i.e. saving binary
558      *                     data in a db)
559      *
560      * Use backslashes to escape placeholder characters if you don't want
561      * them to be interpreted as placeholders.  Example: <code>
562      *    "UPDATE foo SET col=? WHERE col='over \& under'"
563      * </code>
564      *
565      * @param string $query  the query to be prepared
566      *
567      * @return mixed  DB statement resource on success. DB_Error on failure.
568      *
569      * @see DB_oci8::execute()
570      */
571     public function prepare($query)
572     {
573         $tokens   = preg_split(
574             '/((?<!\\\)[&?!])/',
575             $query,
576             -1,
577             PREG_SPLIT_DELIM_CAPTURE
578         );
579         $binds    = count($tokens) - 1;
580         $token    = 0;
581         $types    = array();
582         $newquery = '';
583
584         foreach ($tokens as $key => $val) {
585             switch ($val) {
586                 case '?':
587                     $types[$token++] = DB_PARAM_SCALAR;
588                     unset($tokens[$key]);
589                     break;
590                 case '&':
591                     $types[$token++] = DB_PARAM_OPAQUE;
592                     unset($tokens[$key]);
593                     break;
594                 case '!':
595                     $types[$token++] = DB_PARAM_MISC;
596                     unset($tokens[$key]);
597                     break;
598                 default:
599                     $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
600                     if ($key != $binds) {
601                         $newquery .= $tokens[$key] . ':bind' . $token;
602                     } else {
603                         $newquery .= $tokens[$key];
604                     }
605             }
606         }
607
608         $this->last_query = $query;
609         $newquery = $this->modifyQuery($newquery);
610         if (!$stmt = @OCIParse($this->connection, $newquery)) {
611             return $this->oci8RaiseError();
612         }
613         $this->prepare_types[(int)$stmt] = $types;
614         $this->manip_query[(int)$stmt] = DB::isManip($query);
615         $this->_prepared_queries[(int)$stmt] = $newquery;
616         return $stmt;
617     }
618
619     // }}}
620     // {{{ execute()
621
622     /**
623      * Executes a DB statement prepared with prepare().
624      *
625      * To determine how many rows of a result set get buffered using
626      * ocisetprefetch(), see the "result_buffering" option in setOptions().
627      * This option was added in Release 1.7.0.
628      *
629      * @param resource  $stmt  a DB statement resource returned from prepare()
630      * @param mixed  $data  array, string or numeric data to be used in
631      *                      execution of the statement.  Quantity of items
632      *                      passed must match quantity of placeholders in
633      *                      query:  meaning 1 for non-array items or the
634      *                      quantity of elements in the array.
635      *
636      * @return mixed  returns an oic8 result resource for successful SELECT
637      *                queries, DB_OK for other successful queries.
638      *                A DB error object is returned on failure.
639      *
640      * @see DB_oci8::prepare()
641      */
642     public function &execute($stmt, $data = array())
643     {
644         $data = (array)$data;
645         $this->last_parameters = $data;
646         $this->last_query = $this->_prepared_queries[(int)$stmt];
647         $this->_data = $data;
648
649         $types = $this->prepare_types[(int)$stmt];
650         if (count($types) != count($data)) {
651             $tmp = $this->raiseError(DB_ERROR_MISMATCH);
652             return $tmp;
653         }
654
655         $i = 0;
656         foreach ($data as $key => $value) {
657             if ($types[$i] == DB_PARAM_MISC) {
658                 /*
659                  * Oracle doesn't seem to have the ability to pass a
660                  * parameter along unchanged, so strip off quotes from start
661                  * and end, plus turn two single quotes to one single quote,
662                  * in order to avoid the quotes getting escaped by
663                  * Oracle and ending up in the database.
664                  */
665                 $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]);
666                 $data[$key] = str_replace("''", "'", $data[$key]);
667             } elseif ($types[$i] == DB_PARAM_OPAQUE) {
668                 $fp = @fopen($data[$key], 'rb');
669                 if (!$fp) {
670                     $tmp = $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
671                     return $tmp;
672                 }
673                 $data[$key] = fread($fp, filesize($data[$key]));
674                 fclose($fp);
675             } elseif ($types[$i] == DB_PARAM_SCALAR) {
676                 // Floats have to be converted to a locale-neutral
677                 // representation.
678                 if (is_float($data[$key])) {
679                     $data[$key] = $this->quoteFloat($data[$key]);
680                 }
681             }
682             if (!@OCIBindByName($stmt, ':bind' . $i, $data[$key], -1)) {
683                 $tmp = $this->oci8RaiseError($stmt);
684                 return $tmp;
685             }
686             $this->last_query = preg_replace(
687                 "/:bind$i(?!\d)/",
688                 $this->quoteSmart($data[$key]),
689                 $this->last_query,
690                 1
691             );
692             $i++;
693         }
694         if ($this->autocommit) {
695             $success = @OCIExecute($stmt, OCI_COMMIT_ON_SUCCESS);
696         } else {
697             $success = @OCIExecute($stmt, OCI_DEFAULT);
698         }
699         if (!$success) {
700             $tmp = $this->oci8RaiseError($stmt);
701             return $tmp;
702         }
703         $this->last_stmt = $stmt;
704         if ($this->manip_query[(int)$stmt] || $this->_next_query_manip) {
705             $this->_last_query_manip = true;
706             $this->_next_query_manip = false;
707             $tmp = DB_OK;
708         } else {
709             $this->_last_query_manip = false;
710             @ocisetprefetch($stmt, $this->options['result_buffering']);
711             $tmp = new DB_result($this, $stmt);
712         }
713         return $tmp;
714     }
715
716     // }}}
717     // {{{ autoCommit()
718
719     /**
720      * Enables or disables automatic commits
721      *
722      * @param bool $onoff  true turns it on, false turns it off
723      *
724      * @return int  DB_OK on success.  A DB_Error object if the driver
725      *               doesn't support auto-committing transactions.
726      */
727     public function autoCommit($onoff = false)
728     {
729         $this->autocommit = (bool)$onoff;
730         ;
731         return DB_OK;
732     }
733
734     // }}}
735     // {{{ commit()
736
737     /**
738      * Commits the current transaction
739      *
740      * @return int  DB_OK on success.  A DB_Error object on failure.
741      */
742     public function commit()
743     {
744         $result = @OCICommit($this->connection);
745         if (!$result) {
746             return $this->oci8RaiseError();
747         }
748         return DB_OK;
749     }
750
751     // }}}
752     // {{{ rollback()
753
754     /**
755      * Reverts the current transaction
756      *
757      * @return int  DB_OK on success.  A DB_Error object on failure.
758      */
759     public function rollback()
760     {
761         $result = @OCIRollback($this->connection);
762         if (!$result) {
763             return $this->oci8RaiseError();
764         }
765         return DB_OK;
766     }
767
768     // }}}
769     // {{{ affectedRows()
770
771     /**
772      * Determines the number of rows affected by a data maniuplation query
773      *
774      * 0 is returned for queries that don't manipulate data.
775      *
776      * @return int  the number of rows.  A DB_Error object on failure.
777      */
778     public function affectedRows()
779     {
780         if ($this->last_stmt === false) {
781             return $this->oci8RaiseError();
782         }
783         $result = @OCIRowCount($this->last_stmt);
784         if ($result === false) {
785             return $this->oci8RaiseError($this->last_stmt);
786         }
787         return $result;
788     }
789
790     // }}}
791     // {{{ modifyQuery()
792
793     /**
794      * Changes a query string for various DBMS specific reasons
795      *
796      * "SELECT 2+2" must be "SELECT 2+2 FROM dual" in Oracle.
797      *
798      * @param string $query  the query string to modify
799      *
800      * @return string  the modified query string
801      *
802      * @access protected
803      */
804     public function modifyQuery($query)
805     {
806         if (preg_match('/^\s*SELECT/i', $query) &&
807             !preg_match('/\sFROM\s/i', $query)) {
808             $query .= ' FROM dual';
809         }
810         return $query;
811     }
812
813     // }}}
814     // {{{ modifyLimitQuery()
815
816     /**
817      * Adds LIMIT clauses to a query string according to current DBMS standards
818      *
819      * @param string $query   the query to modify
820      * @param int    $from    the row to start to fetching (0 = the first row)
821      * @param int    $count   the numbers of rows to fetch
822      * @param mixed  $params  array, string or numeric data to be used in
823      *                         execution of the statement.  Quantity of items
824      *                         passed must match quantity of placeholders in
825      *                         query:  meaning 1 placeholder for non-array
826      *                         parameters or 1 placeholder per array element.
827      *
828      * @return string  the query string with LIMIT clauses added
829      *
830      * @access protected
831      */
832     public function modifyLimitQuery($query, $from, $count, $params = array())
833     {
834         // Let Oracle return the name of the columns instead of
835         // coding a "home" SQL parser
836
837         if (count($params)) {
838             $result = $this->prepare("SELECT * FROM ($query) "
839                                      . 'WHERE NULL = NULL');
840             $tmp = $this->execute($result, $params);
841         } else {
842             $q_fields = "SELECT * FROM ($query) WHERE NULL = NULL";
843
844             if (!$result = @OCIParse($this->connection, $q_fields)) {
845                 $this->last_query = $q_fields;
846                 return $this->oci8RaiseError();
847             }
848             if (!@OCIExecute($result, OCI_DEFAULT)) {
849                 $this->last_query = $q_fields;
850                 return $this->oci8RaiseError($result);
851             }
852         }
853
854         $ncols = OCINumCols($result);
855         $cols  = array();
856         for ($i = 1; $i <= $ncols; $i++) {
857             $cols[] = '"' . OCIColumnName($result, $i) . '"';
858         }
859         $fields = implode(', ', $cols);
860         // XXX Test that (tip by John Lim)
861         //if (preg_match('/^\s*SELECT\s+/is', $query, $match)) {
862         //    // Introduce the FIRST_ROWS Oracle query optimizer
863         //    $query = substr($query, strlen($match[0]), strlen($query));
864         //    $query = "SELECT /* +FIRST_ROWS */ " . $query;
865         //}
866
867         // Construct the query
868         // more at: http://marc.theaimsgroup.com/?l=php-db&m=99831958101212&w=2
869         // Perhaps this could be optimized with the use of Unions
870         $query = "SELECT $fields FROM".
871                  "  (SELECT rownum as linenum, $fields FROM".
872                  "      ($query)".
873                  '  WHERE rownum <= '. ($from + $count) .
874                  ') WHERE linenum >= ' . ++$from;
875         return $query;
876     }
877
878     // }}}
879     // {{{ nextId()
880
881     /**
882      * Returns the next free id in a sequence
883      *
884      * @param string  $seq_name  name of the sequence
885      * @param boolean $ondemand  when true, the seqence is automatically
886      *                            created if it does not exist
887      *
888      * @return int  the next id number in the sequence.
889      *               A DB_Error object on failure.
890      *
891      * @see DB_common::nextID(), DB_common::getSequenceName(),
892      *      DB_oci8::createSequence(), DB_oci8::dropSequence()
893      */
894     public function nextId($seq_name, $ondemand = true)
895     {
896         $seqname = $this->getSequenceName($seq_name);
897         $repeat = 0;
898         do {
899             $this->expectError(DB_ERROR_NOSUCHTABLE);
900             $result = $this->query("SELECT ${seqname}.nextval FROM dual");
901             $this->popExpect();
902             if ($ondemand && DB::isError($result) &&
903                 $result->getCode() == DB_ERROR_NOSUCHTABLE) {
904                 $repeat = 1;
905                 $result = $this->createSequence($seq_name);
906                 if (DB::isError($result)) {
907                     return $this->raiseError($result);
908                 }
909             } else {
910                 $repeat = 0;
911             }
912         } while ($repeat);
913         if (DB::isError($result)) {
914             return $this->raiseError($result);
915         }
916         $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
917         return $arr[0];
918     }
919
920     /**
921      * Creates a new sequence
922      *
923      * @param string $seq_name  name of the new sequence
924      *
925      * @return int  DB_OK on success.  A DB_Error object on failure.
926      *
927      * @see DB_common::createSequence(), DB_common::getSequenceName(),
928      *      DB_oci8::nextID(), DB_oci8::dropSequence()
929      */
930     public function createSequence($seq_name)
931     {
932         return $this->query('CREATE SEQUENCE '
933                             . $this->getSequenceName($seq_name));
934     }
935
936     // }}}
937     // {{{ dropSequence()
938
939     /**
940      * Deletes a sequence
941      *
942      * @param string $seq_name  name of the sequence to be deleted
943      *
944      * @return int  DB_OK on success.  A DB_Error object on failure.
945      *
946      * @see DB_common::dropSequence(), DB_common::getSequenceName(),
947      *      DB_oci8::nextID(), DB_oci8::createSequence()
948      */
949     public function dropSequence($seq_name)
950     {
951         return $this->query('DROP SEQUENCE '
952                             . $this->getSequenceName($seq_name));
953     }
954
955     // }}}
956     // {{{ oci8RaiseError()
957
958     /**
959      * Produces a DB_Error object regarding the current problem
960      *
961      * @param int $errno  if the error is being manually raised pass a
962      *                     DB_ERROR* constant here.  If this isn't passed
963      *                     the error information gathered from the DBMS.
964      *
965      * @return object  the DB_Error object
966      *
967      * @see DB_common::raiseError(),
968      *      DB_oci8::errorNative(), DB_oci8::errorCode()
969      */
970     public function oci8RaiseError($errno = null)
971     {
972         if ($errno === null) {
973             $error = @OCIError($this->connection);
974             return $this->raiseError(
975                 $this->errorCode($error['code']),
976                 null,
977                 null,
978                 null,
979                 $error['message']
980             );
981         } elseif (is_resource($errno)) {
982             $error = @OCIError($errno);
983             return $this->raiseError(
984                 $this->errorCode($error['code']),
985                 null,
986                 null,
987                 null,
988                 $error['message']
989             );
990         }
991         return $this->raiseError($this->errorCode($errno));
992     }
993
994     // }}}
995     // {{{ errorNative()
996
997     /**
998      * Gets the DBMS' native error code produced by the last query
999      *
1000      * @return int  the DBMS' error code.  FALSE if the code could not be
1001      *               determined
1002      */
1003     public function errorNative()
1004     {
1005         if (is_resource($this->last_stmt)) {
1006             $error = @OCIError($this->last_stmt);
1007         } else {
1008             $error = @OCIError($this->connection);
1009         }
1010         if (is_array($error)) {
1011             return $error['code'];
1012         }
1013         return false;
1014     }
1015
1016     // }}}
1017     // {{{ tableInfo()
1018
1019     /**
1020      * Returns information about a table or a result set
1021      *
1022      * NOTE: only supports 'table' and 'flags' if <var>$result</var>
1023      * is a table name.
1024      *
1025      * NOTE: flags won't contain index information.
1026      *
1027      * @param object|string  $result  DB_result object from a query or a
1028      *                                 string containing the name of a table.
1029      *                                 While this also accepts a query result
1030      *                                 resource identifier, this behavior is
1031      *                                 deprecated.
1032      * @param int            $mode    a valid tableInfo mode
1033      *
1034      * @return array  an associative array with the information requested.
1035      *                 A DB_Error object on failure.
1036      *
1037      * @see DB_common::tableInfo()
1038      */
1039     public function tableInfo($result, $mode = null)
1040     {
1041         if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
1042             $case_func = 'strtolower';
1043         } else {
1044             $case_func = 'strval';
1045         }
1046
1047         $res = array();
1048
1049         if (is_string($result)) {
1050             /*
1051              * Probably received a table name.
1052              * Create a result resource identifier.
1053              */
1054             $result = strtoupper($result);
1055             $q_fields = 'SELECT column_name, data_type, data_length, '
1056                         . 'nullable '
1057                         . 'FROM user_tab_columns '
1058                         . "WHERE table_name='$result' ORDER BY column_id";
1059
1060             $this->last_query = $q_fields;
1061
1062             if (!$stmt = @OCIParse($this->connection, $q_fields)) {
1063                 return $this->oci8RaiseError(DB_ERROR_NEED_MORE_DATA);
1064             }
1065             if (!@OCIExecute($stmt, OCI_DEFAULT)) {
1066                 return $this->oci8RaiseError($stmt);
1067             }
1068             
1069             $i = 0;
1070             while (@OCIFetch($stmt)) {
1071                 $res[$i] = array(
1072                     'table' => $case_func($result),
1073                     'name'  => $case_func(@OCIResult($stmt, 1)),
1074                     'type'  => @OCIResult($stmt, 2),
1075                     'len'   => @OCIResult($stmt, 3),
1076                     'flags' => (@OCIResult($stmt, 4) == 'N') ? 'not_null' : '',
1077                 );
1078                 if ($mode & DB_TABLEINFO_ORDER) {
1079                     $res['order'][$res[$i]['name']] = $i;
1080                 }
1081                 if ($mode & DB_TABLEINFO_ORDERTABLE) {
1082                     $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
1083                 }
1084                 $i++;
1085             }
1086
1087             if ($mode) {
1088                 $res['num_fields'] = $i;
1089             }
1090             @OCIFreeStatement($stmt);
1091         } else {
1092             if (isset($result->result)) {
1093                 /*
1094                  * Probably received a result object.
1095                  * Extract the result resource identifier.
1096                  */
1097                 $result = $result->result;
1098             }
1099
1100             $res = array();
1101
1102             if ($result === $this->last_stmt) {
1103                 $count = @OCINumCols($result);
1104                 if ($mode) {
1105                     $res['num_fields'] = $count;
1106                 }
1107                 for ($i = 0; $i < $count; $i++) {
1108                     $res[$i] = array(
1109                         'table' => '',
1110                         'name'  => $case_func(@OCIColumnName($result, $i+1)),
1111                         'type'  => @OCIColumnType($result, $i+1),
1112                         'len'   => @OCIColumnSize($result, $i+1),
1113                         'flags' => '',
1114                     );
1115                     if ($mode & DB_TABLEINFO_ORDER) {
1116                         $res['order'][$res[$i]['name']] = $i;
1117                     }
1118                     if ($mode & DB_TABLEINFO_ORDERTABLE) {
1119                         $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
1120                     }
1121                 }
1122             } else {
1123                 return $this->raiseError(DB_ERROR_NOT_CAPABLE);
1124             }
1125         }
1126         return $res;
1127     }
1128
1129     // }}}
1130     // {{{ getSpecialQuery()
1131
1132     /**
1133      * Obtains the query string needed for listing a given type of objects
1134      *
1135      * @param string $type  the kind of objects you want to retrieve
1136      *
1137      * @return string  the SQL query string or null if the driver doesn't
1138      *                  support the object type requested
1139      *
1140      * @access protected
1141      * @see DB_common::getListOf()
1142      */
1143     public function getSpecialQuery($type)
1144     {
1145         switch ($type) {
1146             case 'tables':
1147                 return 'SELECT table_name FROM user_tables';
1148             case 'synonyms':
1149                 return 'SELECT synonym_name FROM user_synonyms';
1150             case 'views':
1151                 return 'SELECT view_name FROM user_views';
1152             default:
1153                 return null;
1154         }
1155     }
1156
1157     // }}}
1158     // {{{ quoteFloat()
1159
1160     /**
1161      * Formats a float value for use within a query in a locale-independent
1162      * manner.
1163      *
1164      * @param float the float value to be quoted.
1165      * @return string the quoted string.
1166      * @see DB_common::quoteSmart()
1167      * @since Method available since release 1.7.8.
1168      */
1169     public function quoteFloat($float)
1170     {
1171         return $this->escapeSimple(str_replace(',', '.', strval(floatval($float))));
1172     }
1173      
1174     // }}}
1175 }
1176
1177 /*
1178  * Local variables:
1179  * tab-width: 4
1180  * c-basic-offset: 4
1181  * End:
1182  */