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