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