]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/DB/ibase.php
[ROUTES] Allow accept-header specification during router creation
[quix0rs-gnu-social.git] / extlib / DB / ibase.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6  * The PEAR DB driver for PHP's interbase extension
7  * for interacting with Interbase and Firebird databases
8  *
9  * While this class works with PHP 4, PHP's InterBase extension is
10  * unstable in PHP 4.  Use PHP 5.
11  *
12  * PHP version 5
13  *
14  * LICENSE: This source file is subject to version 3.0 of the PHP license
15  * that is available through the world-wide-web at the following URI:
16  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
17  * the PHP License and are unable to obtain it through the web, please
18  * send a note to license@php.net so we can mail you a copy immediately.
19  *
20  * @category   Database
21  * @package    DB
22  * @author     Sterling Hughes <sterling@php.net>
23  * @author     Daniel Convissor <danielc@php.net>
24  * @copyright  1997-2007 The PHP Group
25  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
26  * @version    CVS: $Id$
27  * @link       http://pear.php.net/package/DB
28  */
29
30 /**
31  * Obtain the DB_common class so it can be extended from
32  */
33 //require_once 'DB/common.php';
34 require_once 'common.php';
35
36 /**
37  * The methods PEAR DB uses to interact with PHP's interbase extension
38  * for interacting with Interbase and Firebird databases
39  *
40  * These methods overload the ones declared in DB_common.
41  *
42  * While this class works with PHP 4, PHP's InterBase extension is
43  * unstable in PHP 4.  Use PHP 5.
44  *
45  * NOTICE:  limitQuery() only works for Firebird.
46  *
47  * @category   Database
48  * @package    DB
49  * @author     Sterling Hughes <sterling@php.net>
50  * @author     Daniel Convissor <danielc@php.net>
51  * @copyright  1997-2007 The PHP Group
52  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
53  * @version    Release: 1.9.2
54  * @link       http://pear.php.net/package/DB
55  * @since      Class became stable in Release 1.7.0
56  */
57 class DB_ibase extends DB_common
58 {
59     // {{{ properties
60
61     /**
62      * The DB driver type (mysql, oci8, odbc, etc.)
63      * @var string
64      */
65     public $phptype = 'ibase';
66
67     /**
68      * The database syntax variant to be used (db2, access, etc.), if any
69      * @var string
70      */
71     public $dbsyntax = 'ibase';
72
73     /**
74      * The capabilities of this DB implementation
75      *
76      * The 'new_link' element contains the PHP version that first provided
77      * new_link support for this DBMS.  Contains false if it's unsupported.
78      *
79      * Meaning of the 'limit' element:
80      *   + 'emulate' = emulate with fetch row by number
81      *   + 'alter'   = alter the query
82      *   + false     = skip rows
83      *
84      * NOTE: only firebird supports limit.
85      *
86      * @var array
87      */
88     public $features = array(
89         'limit' => false,
90         'new_link' => false,
91         'numrows' => 'emulate',
92         'pconnect' => true,
93         'prepare' => true,
94         'ssl' => false,
95         'transactions' => true,
96     );
97
98     /**
99      * A mapping of native error codes to DB error codes
100      * @var array
101      */
102     public $errorcode_map = array(
103         -104 => DB_ERROR_SYNTAX,
104         -150 => DB_ERROR_ACCESS_VIOLATION,
105         -151 => DB_ERROR_ACCESS_VIOLATION,
106         -155 => DB_ERROR_NOSUCHTABLE,
107         -157 => DB_ERROR_NOSUCHFIELD,
108         -158 => DB_ERROR_VALUE_COUNT_ON_ROW,
109         -170 => DB_ERROR_MISMATCH,
110         -171 => DB_ERROR_MISMATCH,
111         -172 => DB_ERROR_INVALID,
112         // -204 =>  // Covers too many errors, need to use regex on msg
113         -205 => DB_ERROR_NOSUCHFIELD,
114         -206 => DB_ERROR_NOSUCHFIELD,
115         -208 => DB_ERROR_INVALID,
116         -219 => DB_ERROR_NOSUCHTABLE,
117         -297 => DB_ERROR_CONSTRAINT,
118         -303 => DB_ERROR_INVALID,
119         -413 => DB_ERROR_INVALID_NUMBER,
120         -530 => DB_ERROR_CONSTRAINT,
121         -551 => DB_ERROR_ACCESS_VIOLATION,
122         -552 => DB_ERROR_ACCESS_VIOLATION,
123         // -607 =>  // Covers too many errors, need to use regex on msg
124         -625 => DB_ERROR_CONSTRAINT_NOT_NULL,
125         -803 => DB_ERROR_CONSTRAINT,
126         -804 => DB_ERROR_VALUE_COUNT_ON_ROW,
127         // -902 =>  // Covers too many errors, need to use regex on msg
128         -904 => DB_ERROR_CONNECT_FAILED,
129         -922 => DB_ERROR_NOSUCHDB,
130         -923 => DB_ERROR_CONNECT_FAILED,
131         -924 => DB_ERROR_CONNECT_FAILED
132     );
133
134     /**
135      * The raw database connection created by PHP
136      * @var resource
137      */
138     public $connection;
139
140     /**
141      * The DSN information for connecting to a database
142      * @var array
143      */
144     public $dsn = array();
145
146
147     /**
148      * The number of rows affected by a data manipulation query
149      * @var integer
150      * @access private
151      */
152     public $affected = 0;
153
154     /**
155      * Should data manipulation queries be committed automatically?
156      * @var bool
157      * @access private
158      */
159     public $autocommit = true;
160
161     /**
162      * The prepared statement handle from the most recently executed statement
163      *
164      * {@internal  Mainly here because the InterBase/Firebird API is only
165      * able to retrieve data from result sets if the statemnt handle is
166      * still in scope.}}
167      *
168      * @var resource
169      */
170     public $last_stmt;
171
172     /**
173      * Is the given prepared statement a data manipulation query?
174      * @var array
175      * @access private
176      */
177     public $manip_query = array();
178
179
180     // }}}
181     // {{{ constructor
182
183     /**
184      * This constructor calls <kbd>parent::__construct()</kbd>
185      *
186      * @return void
187      */
188     public function __construct()
189     {
190         parent::__construct();
191     }
192
193     // }}}
194     // {{{ connect()
195
196     /**
197      * Connect to the database server, log in and open the database
198      *
199      * Don't call this method directly.  Use DB::connect() instead.
200      *
201      * PEAR DB's ibase driver supports the following extra DSN options:
202      *   + buffers    The number of database buffers to allocate for the
203      *                 server-side cache.
204      *   + charset    The default character set for a database.
205      *   + dialect    The default SQL dialect for any statement
206      *                 executed within a connection.  Defaults to the
207      *                 highest one supported by client libraries.
208      *                 Functional only with InterBase 6 and up.
209      *   + role       Functional only with InterBase 5 and up.
210      *
211      * @param array $dsn the data source name
212      * @param bool $persistent should the connection be persistent?
213      *
214      * @return int|object
215      */
216     public function connect($dsn, $persistent = false)
217     {
218         if (!PEAR::loadExtension('interbase')) {
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         if ($this->dbsyntax == 'firebird') {
227             $this->features['limit'] = 'alter';
228         }
229
230         $params = array(
231             $dsn['hostspec']
232                 ? ($dsn['hostspec'] . ':' . $dsn['database'])
233                 : $dsn['database'],
234             $dsn['username'] ? $dsn['username'] : null,
235             $dsn['password'] ? $dsn['password'] : null,
236             isset($dsn['charset']) ? $dsn['charset'] : null,
237             isset($dsn['buffers']) ? $dsn['buffers'] : null,
238             isset($dsn['dialect']) ? $dsn['dialect'] : null,
239             isset($dsn['role']) ? $dsn['role'] : null,
240         );
241
242         $connect_function = $persistent ? 'ibase_pconnect' : 'ibase_connect';
243
244         $this->connection = @call_user_func_array($connect_function, $params);
245         if (!$this->connection) {
246             return $this->ibaseRaiseError(DB_ERROR_CONNECT_FAILED);
247         }
248         return DB_OK;
249     }
250
251     // }}}
252     // {{{ disconnect()
253
254     /**
255      * Produces a DB_Error object regarding the current problem
256      *
257      * @param int $errno if the error is being manually raised pass a
258      *                     DB_ERROR* constant here.  If this isn't passed
259      *                     the error information gathered from the DBMS.
260      *
261      * @return object  the DB_Error object
262      *
263      * @see DB_common::raiseError(),
264      *      DB_ibase::errorNative(), DB_ibase::errorCode()
265      */
266     public function &ibaseRaiseError($errno = null)
267     {
268         if ($errno === null) {
269             $errno = $this->errorCode($this->errorNative());
270         }
271         $tmp = $this->raiseError($errno, null, null, null, @ibase_errmsg());
272         return $tmp;
273     }
274
275     // }}}
276     // {{{ simpleQuery()
277
278     /**
279      * Maps native error codes to DB's portable ones
280      *
281      * @param int $nativecode the error code returned by the DBMS
282      *
283      * @return int  the portable DB error code.  Return DB_ERROR if the
284      *               current driver doesn't have a mapping for the
285      *               $nativecode submitted.
286      *
287      * @since Method available since Release 1.7.0
288      */
289     public function errorCode($nativecode = null)
290     {
291         if (isset($this->errorcode_map[$nativecode])) {
292             return $this->errorcode_map[$nativecode];
293         }
294
295         static $error_regexps;
296         if (!isset($error_regexps)) {
297             $error_regexps = array(
298                 '/generator .* is not defined/'
299                 => DB_ERROR_SYNTAX,  // for compat. w ibase_errcode()
300                 '/violation of [\w ]+ constraint/i'
301                 => DB_ERROR_CONSTRAINT,
302                 '/table.*(not exist|not found|unknown)/i'
303                 => DB_ERROR_NOSUCHTABLE,
304                 '/table .* already exists/i'
305                 => DB_ERROR_ALREADY_EXISTS,
306                 '/unsuccessful metadata update .* failed attempt to store duplicate value/i'
307                 => DB_ERROR_ALREADY_EXISTS,
308                 '/unsuccessful metadata update .* not found/i'
309                 => DB_ERROR_NOT_FOUND,
310                 '/validation error for column .* value "\*\*\* null/i'
311                 => DB_ERROR_CONSTRAINT_NOT_NULL,
312                 '/conversion error from string/i'
313                 => DB_ERROR_INVALID_NUMBER,
314                 '/no permission for/i'
315                 => DB_ERROR_ACCESS_VIOLATION,
316                 '/arithmetic exception, numeric overflow, or string truncation/i'
317                 => DB_ERROR_INVALID,
318                 '/feature is not supported/i'
319                 => DB_ERROR_NOT_CAPABLE,
320             );
321         }
322
323         $errormsg = @ibase_errmsg();
324         foreach ($error_regexps as $regexp => $code) {
325             if (preg_match($regexp, $errormsg)) {
326                 return $code;
327             }
328         }
329         return DB_ERROR;
330     }
331
332     // }}}
333     // {{{ modifyLimitQuery()
334
335     /**
336      * Gets the DBMS' native error code produced by the last query
337      *
338      * @return int  the DBMS' error code.  NULL if there is no error code.
339      *
340      * @since Method available since Release 1.7.0
341      */
342     public function errorNative()
343     {
344         if (function_exists('ibase_errcode')) {
345             return @ibase_errcode();
346         }
347         if (preg_match(
348             '/^Dynamic SQL Error SQL error code = ([0-9-]+)/i',
349             @ibase_errmsg(),
350             $m
351         )) {
352             return (int)$m[1];
353         }
354         return null;
355     }
356
357     // }}}
358     // {{{ nextResult()
359
360     /**
361      * Disconnects from the database server
362      *
363      * @return bool  TRUE on success, FALSE on failure
364      */
365     public function disconnect()
366     {
367         $ret = @ibase_close($this->connection);
368         $this->connection = null;
369         return $ret;
370     }
371
372     // }}}
373     // {{{ fetchInto()
374
375     /**
376      * Sends a query to the database server
377      *
378      * @param string  the SQL query string
379      *
380      * @return mixed  + a PHP result resrouce for successful SELECT queries
381      *                + the DB_OK constant for other successful queries
382      *                + a DB_Error object on failure
383      */
384     public function simpleQuery($query)
385     {
386         $ismanip = $this->_checkManip($query);
387         $this->last_query = $query;
388         $query = $this->modifyQuery($query);
389         $result = @ibase_query($this->connection, $query);
390
391         if (!$result) {
392             return $this->ibaseRaiseError();
393         }
394         if ($this->autocommit && $ismanip) {
395             @ibase_commit($this->connection);
396         }
397         if ($ismanip) {
398             $this->affected = $result;
399             return DB_OK;
400         } else {
401             $this->affected = 0;
402             return $result;
403         }
404     }
405
406     // }}}
407     // {{{ freeResult()
408
409     /**
410      * Adds LIMIT clauses to a query string according to current DBMS standards
411      *
412      * Only works with Firebird.
413      *
414      * @param string $query the query to modify
415      * @param int $from the row to start to fetching (0 = the first row)
416      * @param int $count the numbers of rows to fetch
417      * @param mixed $params array, string or numeric data to be used in
418      *                         execution of the statement.  Quantity of items
419      *                         passed must match quantity of placeholders in
420      *                         query:  meaning 1 placeholder for non-array
421      *                         parameters or 1 placeholder per array element.
422      *
423      * @return string  the query string with LIMIT clauses added
424      *
425      * @access protected
426      */
427     public function modifyLimitQuery($query, $from, $count, $params = array())
428     {
429         if ($this->dsn['dbsyntax'] == 'firebird') {
430             $query = preg_replace(
431                 '/^([\s(])*SELECT/i',
432                 "SELECT FIRST $count SKIP $from",
433                 $query
434             );
435         }
436         return $query;
437     }
438
439     // }}}
440     // {{{ freeQuery()
441
442     /**
443      * Move the internal ibase result pointer to the next available result
444      *
445      * @param a valid fbsql result resource
446      *
447      * @access public
448      *
449      * @return true if a result is available otherwise return false
450      */
451     public function nextResult($result)
452     {
453         return false;
454     }
455
456     // }}}
457     // {{{ affectedRows()
458
459     /**
460      * Places a row from the result set into the given array
461      *
462      * Formating of the array and the data therein are configurable.
463      * See DB_result::fetchInto() for more information.
464      *
465      * This method is not meant to be called directly.  Use
466      * DB_result::fetchInto() instead.  It can't be declared "protected"
467      * because DB_result is a separate object.
468      *
469      * @param resource $result the query result resource
470      * @param array $arr the referenced array to put the data in
471      * @param int $fetchmode how the resulting array should be indexed
472      * @param int $rownum the row number to fetch (0 = first row)
473      *
474      * @return mixed  DB_OK on success, NULL when the end of a result set is
475      *                 reached or on failure
476      *
477      * @see DB_result::fetchInto()
478      */
479     public function fetchInto($result, &$arr, $fetchmode, $rownum = null)
480     {
481         if ($rownum !== null) {
482             return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE);
483         }
484         if ($fetchmode & DB_FETCHMODE_ASSOC) {
485             if (function_exists('ibase_fetch_assoc')) {
486                 $arr = @ibase_fetch_assoc($result);
487             } else {
488                 $arr = get_object_vars(ibase_fetch_object($result));
489             }
490             if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
491                 $arr = array_change_key_case($arr, CASE_LOWER);
492             }
493         } else {
494             $arr = @ibase_fetch_row($result);
495         }
496         if (!$arr) {
497             return null;
498         }
499         if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
500             $this->_rtrimArrayValues($arr);
501         }
502         if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
503             $this->_convertNullArrayValuesToEmpty($arr);
504         }
505         return DB_OK;
506     }
507
508     // }}}
509     // {{{ numCols()
510
511     /**
512      * Deletes the result set and frees the memory occupied by the result set
513      *
514      * This method is not meant to be called directly.  Use
515      * DB_result::free() instead.  It can't be declared "protected"
516      * because DB_result is a separate object.
517      *
518      * @param resource $result PHP's query result resource
519      *
520      * @return bool  TRUE on success, FALSE if $result is invalid
521      *
522      * @see DB_result::free()
523      */
524     public function freeResult($result)
525     {
526         return is_resource($result) ? ibase_free_result($result) : false;
527     }
528
529     // }}}
530     // {{{ prepare()
531
532     public function freeQuery($query)
533     {
534         return is_resource($query) ? ibase_free_query($query) : false;
535     }
536
537     // }}}
538     // {{{ execute()
539
540     /**
541      * Determines the number of rows affected by a data maniuplation query
542      *
543      * 0 is returned for queries that don't manipulate data.
544      *
545      * @return int|object
546      */
547     public function affectedRows()
548     {
549         if (is_integer($this->affected)) {
550             return $this->affected;
551         }
552         return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE);
553     }
554
555     /**
556      * Gets the number of columns in a result set
557      *
558      * This method is not meant to be called directly.  Use
559      * DB_result::numCols() instead.  It can't be declared "protected"
560      * because DB_result is a separate object.
561      *
562      * @param resource $result PHP's query result resource
563      *
564      * @return int|object
565      *
566      * @see DB_result::numCols()
567      */
568     public function numCols($result)
569     {
570         $cols = @ibase_num_fields($result);
571         if (!$cols) {
572             return $this->ibaseRaiseError();
573         }
574         return $cols;
575     }
576
577     // }}}
578     // {{{ autoCommit()
579
580     /**
581      * Prepares a query for multiple execution with execute().
582      *
583      * prepare() requires a generic query as string like <code>
584      *    INSERT INTO numbers VALUES (?, ?, ?)
585      * </code>.  The <kbd>?</kbd> characters are placeholders.
586      *
587      * Three types of placeholders can be used:
588      *   + <kbd>?</kbd>  a quoted scalar value, i.e. strings, integers
589      *   + <kbd>!</kbd>  value is inserted 'as is'
590      *   + <kbd>&</kbd>  requires a file name.  The file's contents get
591      *                     inserted into the query (i.e. saving binary
592      *                     data in a db)
593      *
594      * Use backslashes to escape placeholder characters if you don't want
595      * them to be interpreted as placeholders.  Example: <code>
596      *    "UPDATE foo SET col=? WHERE col='over \& under'"
597      * </code>
598      *
599      * @param string $query query to be prepared
600      * @return mixed DB statement resource on success. DB_Error on failure.
601      */
602     public function prepare($query)
603     {
604         $tokens = preg_split(
605             '/((?<!\\\)[&?!])/',
606             $query,
607             -1,
608             PREG_SPLIT_DELIM_CAPTURE
609         );
610         $token = 0;
611         $types = array();
612         $newquery = '';
613
614         foreach ($tokens as $key => $val) {
615             switch ($val) {
616                 case '?':
617                     $types[$token++] = DB_PARAM_SCALAR;
618                     break;
619                 case '&':
620                     $types[$token++] = DB_PARAM_OPAQUE;
621                     break;
622                 case '!':
623                     $types[$token++] = DB_PARAM_MISC;
624                     break;
625                 default:
626                     $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
627                     $newquery .= $tokens[$key] . '?';
628             }
629         }
630
631         $newquery = substr($newquery, 0, -1);
632         $this->last_query = $query;
633         $newquery = $this->modifyQuery($newquery);
634         $stmt = @ibase_prepare(/*$this->connection,*/ $newquery);
635
636         if ($stmt === false) {
637             $stmt = $this->ibaseRaiseError();
638         } else {
639             $this->prepare_types[(int)$stmt] = $types;
640             $this->manip_query[(int)$stmt] = DB::isManip($query);
641         }
642
643         return $stmt;
644     }
645
646     // }}}
647     // {{{ commit()
648
649     /**
650      * Executes a DB statement prepared with prepare().
651      *
652      * @param resource $stmt a DB statement resource returned from prepare()
653      * @param mixed $data array, string or numeric data to be used in
654      *                      execution of the statement.  Quantity of items
655      *                      passed must match quantity of placeholders in
656      *                      query:  meaning 1 for non-array items or the
657      *                      quantity of elements in the array.
658      * @return object  a new DB_Result or a DB_Error when fail
659      * @see DB_ibase::prepare()
660      * @access public
661      */
662     public function &execute($stmt, $data = array())
663     {
664         $data = (array)$data;
665         $this->last_parameters = $data;
666
667         $types = $this->prepare_types[(int)$stmt];
668         if (count($types) != count($data)) {
669             $tmp = $this->raiseError(DB_ERROR_MISMATCH);
670             return $tmp;
671         }
672
673         $i = 0;
674         foreach ($data as $key => $value) {
675             if ($types[$i] == DB_PARAM_MISC) {
676                 /*
677                  * ibase doesn't seem to have the ability to pass a
678                  * parameter along unchanged, so strip off quotes from start
679                  * and end, plus turn two single quotes to one single quote,
680                  * in order to avoid the quotes getting escaped by
681                  * ibase and ending up in the database.
682                  */
683                 $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]);
684                 $data[$key] = str_replace("''", "'", $data[$key]);
685             } elseif ($types[$i] == DB_PARAM_OPAQUE) {
686                 $fp = @fopen($data[$key], 'rb');
687                 if (!$fp) {
688                     $tmp = $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
689                     return $tmp;
690                 }
691                 $data[$key] = fread($fp, filesize($data[$key]));
692                 fclose($fp);
693             }
694             $i++;
695         }
696
697         array_unshift($data, $stmt);
698
699         $res = call_user_func_array('ibase_execute', $data);
700         if (!$res) {
701             $tmp = $this->ibaseRaiseError();
702             return $tmp;
703         }
704         /* XXX need this?
705         if ($this->autocommit && $this->manip_query[(int)$stmt]) {
706             @ibase_commit($this->connection);
707         }*/
708         $this->last_stmt = $stmt;
709         if ($this->manip_query[(int)$stmt] || $this->_next_query_manip) {
710             $this->_last_query_manip = true;
711             $this->_next_query_manip = false;
712             $tmp = DB_OK;
713         } else {
714             $this->_last_query_manip = false;
715             $tmp = new DB_result($this, $res);
716         }
717         return $tmp;
718     }
719
720     // }}}
721     // {{{ rollback()
722
723     /**
724      * Frees the internal resources associated with a prepared query
725      *
726      * @param resource $stmt the prepared statement's PHP resource
727      * @param bool $free_resource should the PHP resource be freed too?
728      *                                  Use false if you need to get data
729      *                                  from the result set later.
730      *
731      * @return bool  TRUE on success, FALSE if $result is invalid
732      *
733      * @see DB_ibase::prepare()
734      */
735     public function freePrepared($stmt, $free_resource = true)
736     {
737         if (!is_resource($stmt)) {
738             return false;
739         }
740         if ($free_resource) {
741             @ibase_free_query($stmt);
742         }
743         unset($this->prepare_tokens[(int)$stmt]);
744         unset($this->prepare_types[(int)$stmt]);
745         unset($this->manip_query[(int)$stmt]);
746         return true;
747     }
748
749     // }}}
750     // {{{ transactionInit()
751
752     /**
753      * Enables or disables automatic commits
754      *
755      * @param bool $onoff true turns it on, false turns it off
756      *
757      * @return int  DB_OK on success.  A DB_Error object if the driver
758      *               doesn't support auto-committing transactions.
759      */
760     public function autoCommit($onoff = false)
761     {
762         $this->autocommit = $onoff ? 1 : 0;
763         return DB_OK;
764     }
765
766     // }}}
767     // {{{ nextId()
768
769     /**
770      * Commits the current transaction
771      *
772      * @return int  DB_OK on success.  A DB_Error object on failure.
773      */
774     public function commit()
775     {
776         return @ibase_commit($this->connection);
777     }
778
779     // }}}
780     // {{{ createSequence()
781
782     /**
783      * Reverts the current transaction
784      *
785      * @return int  DB_OK on success.  A DB_Error object on failure.
786      */
787     public function rollback()
788     {
789         return @ibase_rollback($this->connection);
790     }
791
792     // }}}
793     // {{{ dropSequence()
794
795     public function transactionInit($trans_args = 0)
796     {
797         return $trans_args
798             ? @ibase_trans($trans_args, $this->connection)
799             : @ibase_trans();
800     }
801
802     // }}}
803     // {{{ _ibaseFieldFlags()
804
805     /**
806      * Returns the next free id in a sequence
807      *
808      * @param string $seq_name name of the sequence
809      * @param boolean $ondemand when true, the seqence is automatically
810      *                            created if it does not exist
811      *
812      * @return int|object
813      *               A DB_Error object on failure.
814      *
815      * @see DB_common::nextID(), DB_common::getSequenceName(),
816      *      DB_ibase::createSequence(), DB_ibase::dropSequence()
817      */
818     public function nextId($seq_name, $ondemand = true)
819     {
820         $sqn = strtoupper($this->getSequenceName($seq_name));
821         $repeat = 0;
822         do {
823             $this->pushErrorHandling(PEAR_ERROR_RETURN);
824             $result = $this->query("SELECT GEN_ID(${sqn}, 1) "
825                 . 'FROM RDB$GENERATORS '
826                 . "WHERE RDB\$GENERATOR_NAME='${sqn}'");
827             $this->popErrorHandling();
828             if ($ondemand && DB::isError($result)) {
829                 $repeat = 1;
830                 $result = $this->createSequence($seq_name);
831                 if (DB::isError($result)) {
832                     return $result;
833                 }
834             } else {
835                 $repeat = 0;
836             }
837         } while ($repeat);
838         if (DB::isError($result)) {
839             return $this->raiseError($result);
840         }
841         $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
842         $result->free();
843         return $arr[0];
844     }
845
846     // }}}
847     // {{{ ibaseRaiseError()
848
849     /**
850      * Creates a new sequence
851      *
852      * @param string $seq_name name of the new sequence
853      *
854      * @return int  DB_OK on success.  A DB_Error object on failure.
855      *
856      * @see DB_common::createSequence(), DB_common::getSequenceName(),
857      *      DB_ibase::nextID(), DB_ibase::dropSequence()
858      */
859     public function createSequence($seq_name)
860     {
861         $sqn = strtoupper($this->getSequenceName($seq_name));
862         $this->pushErrorHandling(PEAR_ERROR_RETURN);
863         $result = $this->query("CREATE GENERATOR ${sqn}");
864         $this->popErrorHandling();
865
866         return $result;
867     }
868
869     // }}}
870     // {{{ errorNative()
871
872     /**
873      * Deletes a sequence
874      *
875      * @param string $seq_name name of the sequence to be deleted
876      *
877      * @return int  DB_OK on success.  A DB_Error object on failure.
878      *
879      * @see DB_common::dropSequence(), DB_common::getSequenceName(),
880      *      DB_ibase::nextID(), DB_ibase::createSequence()
881      */
882     public function dropSequence($seq_name)
883     {
884         return $this->query('DELETE FROM RDB$GENERATORS '
885             . "WHERE RDB\$GENERATOR_NAME='"
886             . strtoupper($this->getSequenceName($seq_name))
887             . "'");
888     }
889
890     // }}}
891     // {{{ errorCode()
892
893     /**
894      * Returns information about a table or a result set
895      *
896      * NOTE: only supports 'table' and 'flags' if <var>$result</var>
897      * is a table name.
898      *
899      * @param object|string $result DB_result object from a query or a
900      *                                 string containing the name of a table.
901      *                                 While this also accepts a query result
902      *                                 resource identifier, this behavior is
903      *                                 deprecated.
904      * @param int $mode a valid tableInfo mode
905      *
906      * @return array|object
907      *                 A DB_Error object on failure.
908      *
909      * @see DB_common::tableInfo()
910      */
911     public function tableInfo($result, $mode = null)
912     {
913         if (is_string($result)) {
914             /*
915              * Probably received a table name.
916              * Create a result resource identifier.
917              */
918             $id = @ibase_query(
919                 $this->connection,
920                 "SELECT * FROM $result WHERE 1=0"
921             );
922             $got_string = true;
923         } elseif (isset($result->result)) {
924             /*
925              * Probably received a result object.
926              * Extract the result resource identifier.
927              */
928             $id = $result->result;
929             $got_string = false;
930         } else {
931             /*
932              * Probably received a result resource identifier.
933              * Copy it.
934              * Deprecated.  Here for compatibility only.
935              */
936             $id = $result;
937             $got_string = false;
938         }
939
940         if (!is_resource($id)) {
941             return $this->ibaseRaiseError(DB_ERROR_NEED_MORE_DATA);
942         }
943
944         if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
945             $case_func = 'strtolower';
946         } else {
947             $case_func = 'strval';
948         }
949
950         $count = @ibase_num_fields($id);
951         $res = array();
952
953         if ($mode) {
954             $res['num_fields'] = $count;
955         }
956
957         for ($i = 0; $i < $count; $i++) {
958             $info = @ibase_field_info($id, $i);
959             $res[$i] = array(
960                 'table' => $got_string ? $case_func($result) : '',
961                 'name' => $case_func($info['name']),
962                 'type' => $info['type'],
963                 'len' => $info['length'],
964                 'flags' => ($got_string)
965                     ? $this->_ibaseFieldFlags($info['name'], $result)
966                     : '',
967             );
968             if ($mode & DB_TABLEINFO_ORDER) {
969                 $res['order'][$res[$i]['name']] = $i;
970             }
971             if ($mode & DB_TABLEINFO_ORDERTABLE) {
972                 $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
973             }
974         }
975
976         // free the result only if we were called on a table
977         if ($got_string) {
978             @ibase_free_result($id);
979         }
980         return $res;
981     }
982
983     // }}}
984     // {{{ tableInfo()
985
986     /**
987      * Get the column's flags
988      *
989      * Supports "primary_key", "unique_key", "not_null", "default",
990      * "computed" and "blob".
991      *
992      * @param string $field_name the name of the field
993      * @param string $table_name the name of the table
994      *
995      * @return string  the flags
996      *
997      * @access private
998      */
999     public function _ibaseFieldFlags($field_name, $table_name)
1000     {
1001         $sql = 'SELECT R.RDB$CONSTRAINT_TYPE CTYPE'
1002             . ' FROM RDB$INDEX_SEGMENTS I'
1003             . '  JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME=R.RDB$INDEX_NAME'
1004             . ' WHERE I.RDB$FIELD_NAME=\'' . $field_name . '\''
1005             . '  AND UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\'';
1006
1007         $result = @ibase_query($this->connection, $sql);
1008         if (!$result) {
1009             return $this->ibaseRaiseError();
1010         }
1011
1012         $flags = '';
1013         if ($obj = @ibase_fetch_object($result)) {
1014             @ibase_free_result($result);
1015             if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'PRIMARY KEY') {
1016                 $flags .= 'primary_key ';
1017             }
1018             if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'UNIQUE') {
1019                 $flags .= 'unique_key ';
1020             }
1021         }
1022
1023         $sql = 'SELECT R.RDB$NULL_FLAG AS NFLAG,'
1024             . '  R.RDB$DEFAULT_SOURCE AS DSOURCE,'
1025             . '  F.RDB$FIELD_TYPE AS FTYPE,'
1026             . '  F.RDB$COMPUTED_SOURCE AS CSOURCE'
1027             . ' FROM RDB$RELATION_FIELDS R '
1028             . '  JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME'
1029             . ' WHERE UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\''
1030             . '  AND R.RDB$FIELD_NAME=\'' . $field_name . '\'';
1031
1032         $result = @ibase_query($this->connection, $sql);
1033         if (!$result) {
1034             return $this->ibaseRaiseError();
1035         }
1036         if ($obj = @ibase_fetch_object($result)) {
1037             @ibase_free_result($result);
1038             if (isset($obj->NFLAG)) {
1039                 $flags .= 'not_null ';
1040             }
1041             if (isset($obj->DSOURCE)) {
1042                 $flags .= 'default ';
1043             }
1044             if (isset($obj->CSOURCE)) {
1045                 $flags .= 'computed ';
1046             }
1047             if (isset($obj->FTYPE) && $obj->FTYPE == 261) {
1048                 $flags .= 'blob ';
1049             }
1050         }
1051
1052         return trim($flags);
1053     }
1054
1055     // }}}
1056     // {{{ getSpecialQuery()
1057
1058     /**
1059      * Obtains the query string needed for listing a given type of objects
1060      *
1061      * @param string $type the kind of objects you want to retrieve
1062      *
1063      * @return string  the SQL query string or null if the driver doesn't
1064      *                  support the object type requested
1065      *
1066      * @access protected
1067      * @see DB_common::getListOf()
1068      */
1069     public function getSpecialQuery($type)
1070     {
1071         switch ($type) {
1072             case 'tables':
1073                 return 'SELECT DISTINCT R.RDB$RELATION_NAME FROM '
1074                     . 'RDB$RELATION_FIELDS R WHERE R.RDB$SYSTEM_FLAG=0';
1075             case 'views':
1076                 return 'SELECT DISTINCT RDB$VIEW_NAME from RDB$VIEW_RELATIONS';
1077             case 'users':
1078                 return 'SELECT DISTINCT RDB$USER FROM RDB$USER_PRIVILEGES';
1079             default:
1080                 return null;
1081         }
1082     }
1083
1084     // }}}
1085 }
1086
1087 /*
1088  * Local variables:
1089  * tab-width: 4
1090  * c-basic-offset: 4
1091  * End:
1092  */