alecpl
2008-04-29 6ee5ed253b92593ac5784300ac425f34f7fd1728
commit | author | age
1676e1 1 <?php
S 2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6  * The PEAR DB driver for PHP's fbsql extension
7  * for interacting with FrontBase 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     Frank M. Kromann <frank@frontbase.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$
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 fbsql extension
34  * for interacting with FrontBase databases
35  *
36  * These methods overload the ones declared in DB_common.
37  *
38  * @category   Database
39  * @package    DB
40  * @author     Frank M. Kromann <frank@frontbase.com>
41  * @author     Daniel Convissor <danielc@php.net>
42  * @copyright  1997-2005 The PHP Group
43  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
44  * @version    Release: @package_version@
45  * @link       http://pear.php.net/package/DB
46  * @since      Class functional since Release 1.7.0
47  */
48 class DB_fbsql extends DB_common
49 {
50     // {{{ properties
51
52     /**
53      * The DB driver type (mysql, oci8, odbc, etc.)
54      * @var string
55      */
56     var $phptype = 'fbsql';
57
58     /**
59      * The database syntax variant to be used (db2, access, etc.), if any
60      * @var string
61      */
62     var $dbsyntax = 'fbsql';
63
64     /**
65      * The capabilities of this DB implementation
66      *
67      * The 'new_link' element contains the PHP version that first provided
68      * new_link support for this DBMS.  Contains false if it's unsupported.
69      *
70      * Meaning of the 'limit' element:
71      *   + 'emulate' = emulate with fetch row by number
72      *   + 'alter'   = alter the query
73      *   + false     = skip rows
74      *
75      * @var array
76      */
77     var $features = array(
78         'limit'         => 'alter',
79         'new_link'      => false,
80         'numrows'       => true,
81         'pconnect'      => true,
82         'prepare'       => false,
83         'ssl'           => false,
84         'transactions'  => true,
85     );
86
87     /**
88      * A mapping of native error codes to DB error codes
89      * @var array
90      */
91     var $errorcode_map = array(
92          22 => DB_ERROR_SYNTAX,
93          85 => DB_ERROR_ALREADY_EXISTS,
94         108 => DB_ERROR_SYNTAX,
95         116 => DB_ERROR_NOSUCHTABLE,
96         124 => DB_ERROR_VALUE_COUNT_ON_ROW,
97         215 => DB_ERROR_NOSUCHFIELD,
98         217 => DB_ERROR_INVALID_NUMBER,
99         226 => DB_ERROR_NOSUCHFIELD,
100         231 => DB_ERROR_INVALID,
101         239 => DB_ERROR_TRUNCATED,
102         251 => DB_ERROR_SYNTAX,
103         266 => DB_ERROR_NOT_FOUND,
104         357 => DB_ERROR_CONSTRAINT_NOT_NULL,
105         358 => DB_ERROR_CONSTRAINT,
106         360 => DB_ERROR_CONSTRAINT,
107         361 => DB_ERROR_CONSTRAINT,
108     );
109
110     /**
111      * The raw database connection created by PHP
112      * @var resource
113      */
114     var $connection;
115
116     /**
117      * The DSN information for connecting to a database
118      * @var array
119      */
120     var $dsn = array();
121
122
123     // }}}
124     // {{{ constructor
125
126     /**
127      * This constructor calls <kbd>$this->DB_common()</kbd>
128      *
129      * @return void
130      */
131     function DB_fbsql()
132     {
133         $this->DB_common();
134     }
135
136     // }}}
137     // {{{ connect()
138
139     /**
140      * Connect to the database server, log in and open the database
141      *
142      * Don't call this method directly.  Use DB::connect() instead.
143      *
144      * @param array $dsn         the data source name
145      * @param bool  $persistent  should the connection be persistent?
146      *
147      * @return int  DB_OK on success. A DB_Error object on failure.
148      */
149     function connect($dsn, $persistent = false)
150     {
151         if (!PEAR::loadExtension('fbsql')) {
152             return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
153         }
154
155         $this->dsn = $dsn;
156         if ($dsn['dbsyntax']) {
157             $this->dbsyntax = $dsn['dbsyntax'];
158         }
159
160         $params = array(
161             $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost',
162             $dsn['username'] ? $dsn['username'] : null,
163             $dsn['password'] ? $dsn['password'] : null,
164         );
165
166         $connect_function = $persistent ? 'fbsql_pconnect' : 'fbsql_connect';
167
168         $ini = ini_get('track_errors');
169         $php_errormsg = '';
170         if ($ini) {
171             $this->connection = @call_user_func_array($connect_function,
172                                                       $params);
173         } else {
174             ini_set('track_errors', 1);
175             $this->connection = @call_user_func_array($connect_function,
176                                                       $params);
177             ini_set('track_errors', $ini);
178         }
179
180         if (!$this->connection) {
181             return $this->raiseError(DB_ERROR_CONNECT_FAILED,
182                                      null, null, null,
183                                      $php_errormsg);
184         }
185
186         if ($dsn['database']) {
187             if (!@fbsql_select_db($dsn['database'], $this->connection)) {
188                 return $this->fbsqlRaiseError();
189             }
190         }
191
192         return DB_OK;
193     }
194
195     // }}}
196     // {{{ disconnect()
197
198     /**
199      * Disconnects from the database server
200      *
201      * @return bool  TRUE on success, FALSE on failure
202      */
203     function disconnect()
204     {
205         $ret = @fbsql_close($this->connection);
206         $this->connection = null;
207         return $ret;
208     }
209
210     // }}}
211     // {{{ simpleQuery()
212
213     /**
214      * Sends a query to the database server
215      *
216      * @param string  the SQL query string
217      *
218      * @return mixed  + a PHP result resrouce for successful SELECT queries
219      *                + the DB_OK constant for other successful queries
220      *                + a DB_Error object on failure
221      */
222     function simpleQuery($query)
223     {
224         $this->last_query = $query;
225         $query = $this->modifyQuery($query);
226         $result = @fbsql_query("$query;", $this->connection);
227         if (!$result) {
228             return $this->fbsqlRaiseError();
229         }
230         // Determine which queries that should return data, and which
231         // should return an error code only.
232         if (DB::isManip($query)) {
233             return DB_OK;
234         }
235         return $result;
236     }
237
238     // }}}
239     // {{{ nextResult()
240
241     /**
242      * Move the internal fbsql result pointer to the next available result
243      *
244      * @param a valid fbsql result resource
245      *
246      * @access public
247      *
248      * @return true if a result is available otherwise return false
249      */
250     function nextResult($result)
251     {
252         return @fbsql_next_result($result);
253     }
254
255     // }}}
256     // {{{ fetchInto()
257
258     /**
259      * Places a row from the result set into the given array
260      *
261      * Formating of the array and the data therein are configurable.
262      * See DB_result::fetchInto() for more information.
263      *
264      * This method is not meant to be called directly.  Use
265      * DB_result::fetchInto() instead.  It can't be declared "protected"
266      * because DB_result is a separate object.
267      *
268      * @param resource $result    the query result resource
269      * @param array    $arr       the referenced array to put the data in
270      * @param int      $fetchmode how the resulting array should be indexed
271      * @param int      $rownum    the row number to fetch (0 = first row)
272      *
273      * @return mixed  DB_OK on success, NULL when the end of a result set is
274      *                 reached or on failure
275      *
276      * @see DB_result::fetchInto()
277      */
278     function fetchInto($result, &$arr, $fetchmode, $rownum = null)
279     {
280         if ($rownum !== null) {
281             if (!@fbsql_data_seek($result, $rownum)) {
282                 return null;
283             }
284         }
285         if ($fetchmode & DB_FETCHMODE_ASSOC) {
286             $arr = @fbsql_fetch_array($result, FBSQL_ASSOC);
287             if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
288                 $arr = array_change_key_case($arr, CASE_LOWER);
289             }
290         } else {
291             $arr = @fbsql_fetch_row($result);
292         }
293         if (!$arr) {
294             return null;
295         }
296         if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
297             $this->_rtrimArrayValues($arr);
298         }
299         if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
300             $this->_convertNullArrayValuesToEmpty($arr);
301         }
302         return DB_OK;
303     }
304
305     // }}}
306     // {{{ freeResult()
307
308     /**
309      * Deletes the result set and frees the memory occupied by the result set
310      *
311      * This method is not meant to be called directly.  Use
312      * DB_result::free() instead.  It can't be declared "protected"
313      * because DB_result is a separate object.
314      *
315      * @param resource $result  PHP's query result resource
316      *
317      * @return bool  TRUE on success, FALSE if $result is invalid
318      *
319      * @see DB_result::free()
320      */
321     function freeResult($result)
322     {
323         return @fbsql_free_result($result);
324     }
325
326     // }}}
327     // {{{ autoCommit()
328
329     /**
330      * Enables or disables automatic commits
331      *
332      * @param bool $onoff  true turns it on, false turns it off
333      *
334      * @return int  DB_OK on success.  A DB_Error object if the driver
335      *               doesn't support auto-committing transactions.
336      */
337     function autoCommit($onoff=false)
338     {
339         if ($onoff) {
340             $this->query("SET COMMIT TRUE");
341         } else {
342             $this->query("SET COMMIT FALSE");
343         }
344     }
345
346     // }}}
347     // {{{ commit()
348
349     /**
350      * Commits the current transaction
351      *
352      * @return int  DB_OK on success.  A DB_Error object on failure.
353      */
354     function commit()
355     {
356         @fbsql_commit();
357     }
358
359     // }}}
360     // {{{ rollback()
361
362     /**
363      * Reverts the current transaction
364      *
365      * @return int  DB_OK on success.  A DB_Error object on failure.
366      */
367     function rollback()
368     {
369         @fbsql_rollback();
370     }
371
372     // }}}
373     // {{{ numCols()
374
375     /**
376      * Gets the number of columns in a result set
377      *
378      * This method is not meant to be called directly.  Use
379      * DB_result::numCols() instead.  It can't be declared "protected"
380      * because DB_result is a separate object.
381      *
382      * @param resource $result  PHP's query result resource
383      *
384      * @return int  the number of columns.  A DB_Error object on failure.
385      *
386      * @see DB_result::numCols()
387      */
388     function numCols($result)
389     {
390         $cols = @fbsql_num_fields($result);
391         if (!$cols) {
392             return $this->fbsqlRaiseError();
393         }
394         return $cols;
395     }
396
397     // }}}
398     // {{{ numRows()
399
400     /**
401      * Gets the number of rows in a result set
402      *
403      * This method is not meant to be called directly.  Use
404      * DB_result::numRows() instead.  It can't be declared "protected"
405      * because DB_result is a separate object.
406      *
407      * @param resource $result  PHP's query result resource
408      *
409      * @return int  the number of rows.  A DB_Error object on failure.
410      *
411      * @see DB_result::numRows()
412      */
413     function numRows($result)
414     {
415         $rows = @fbsql_num_rows($result);
416         if ($rows === null) {
417             return $this->fbsqlRaiseError();
418         }
419         return $rows;
420     }
421
422     // }}}
423     // {{{ affectedRows()
424
425     /**
426      * Determines the number of rows affected by a data maniuplation query
427      *
428      * 0 is returned for queries that don't manipulate data.
429      *
430      * @return int  the number of rows.  A DB_Error object on failure.
431      */
432     function affectedRows()
433     {
434         if (DB::isManip($this->last_query)) {
435             $result = @fbsql_affected_rows($this->connection);
436         } else {
437             $result = 0;
438         }
439         return $result;
440      }
441
442     // }}}
443     // {{{ nextId()
444
445     /**
446      * Returns the next free id in a sequence
447      *
448      * @param string  $seq_name  name of the sequence
449      * @param boolean $ondemand  when true, the seqence is automatically
450      *                            created if it does not exist
451      *
452      * @return int  the next id number in the sequence.
453      *               A DB_Error object on failure.
454      *
455      * @see DB_common::nextID(), DB_common::getSequenceName(),
456      *      DB_fbsql::createSequence(), DB_fbsql::dropSequence()
457      */
458     function nextId($seq_name, $ondemand = true)
459     {
460         $seqname = $this->getSequenceName($seq_name);
461         do {
462             $repeat = 0;
463             $this->pushErrorHandling(PEAR_ERROR_RETURN);
464             $result = $this->query('SELECT UNIQUE FROM ' . $seqname);
465             $this->popErrorHandling();
466             if ($ondemand && DB::isError($result) &&
467                 $result->getCode() == DB_ERROR_NOSUCHTABLE) {
468                 $repeat = 1;
469                 $result = $this->createSequence($seq_name);
470                 if (DB::isError($result)) {
471                     return $result;
472                 }
473             } else {
474                 $repeat = 0;
475             }
476         } while ($repeat);
477         if (DB::isError($result)) {
478             return $this->fbsqlRaiseError();
479         }
480         $result->fetchInto($tmp, DB_FETCHMODE_ORDERED);
481         return $tmp[0];
482     }
483
484     /**
485      * Creates a new sequence
486      *
487      * @param string $seq_name  name of the new sequence
488      *
489      * @return int  DB_OK on success.  A DB_Error object on failure.
490      *
491      * @see DB_common::createSequence(), DB_common::getSequenceName(),
492      *      DB_fbsql::nextID(), DB_fbsql::dropSequence()
493      */
494     function createSequence($seq_name)
495     {
496         $seqname = $this->getSequenceName($seq_name);
497         $res = $this->query('CREATE TABLE ' . $seqname
498                             . ' (id INTEGER NOT NULL,'
499                             . ' PRIMARY KEY(id))');
500         if ($res) {
501             $res = $this->query('SET UNIQUE = 0 FOR ' . $seqname);
502         }
503         return $res;
504     }
505
506     // }}}
507     // {{{ dropSequence()
508
509     /**
510      * Deletes a sequence
511      *
512      * @param string $seq_name  name of the sequence to be deleted
513      *
514      * @return int  DB_OK on success.  A DB_Error object on failure.
515      *
516      * @see DB_common::dropSequence(), DB_common::getSequenceName(),
517      *      DB_fbsql::nextID(), DB_fbsql::createSequence()
518      */
519     function dropSequence($seq_name)
520     {
521         return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)
522                             . ' RESTRICT');
523     }
524
525     // }}}
526     // {{{ modifyLimitQuery()
527
528     /**
529      * Adds LIMIT clauses to a query string according to current DBMS standards
530      *
531      * @param string $query   the query to modify
532      * @param int    $from    the row to start to fetching (0 = the first row)
533      * @param int    $count   the numbers of rows to fetch
534      * @param mixed  $params  array, string or numeric data to be used in
535      *                         execution of the statement.  Quantity of items
536      *                         passed must match quantity of placeholders in
537      *                         query:  meaning 1 placeholder for non-array
538      *                         parameters or 1 placeholder per array element.
539      *
540      * @return string  the query string with LIMIT clauses added
541      *
542      * @access protected
543      */
544     function modifyLimitQuery($query, $from, $count, $params = array())
545     {
546         if (DB::isManip($query)) {
547             return preg_replace('/^([\s(])*SELECT/i',
548                                 "\\1SELECT TOP($count)", $query);
549         } else {
550             return preg_replace('/([\s(])*SELECT/i',
551                                 "\\1SELECT TOP($from, $count)", $query);
552         }
553     }
554
555     // }}}
556     // {{{ quoteSmart()
557
558     /**
559      * Formats input so it can be safely used in a query
560      *
561      * @param mixed $in  the data to be formatted
562      *
563      * @return mixed  the formatted data.  The format depends on the input's
564      *                 PHP type:
565      *                 + null = the string <samp>NULL</samp>
566      *                 + boolean = string <samp>TRUE</samp> or <samp>FALSE</samp>
567      *                 + integer or double = the unquoted number
568      *                 + other (including strings and numeric strings) =
569      *                   the data escaped according to FrontBase's settings
570      *                   then encapsulated between single quotes
571      *
572      * @see DB_common::quoteSmart()
573      * @since Method available since Release 1.6.0
574      */
575     function quoteSmart($in)
576     {
577         if (is_int($in) || is_double($in)) {
578             return $in;
579         } elseif (is_bool($in)) {
580             return $in ? 'TRUE' : 'FALSE';
581         } elseif (is_null($in)) {
582             return 'NULL';
583         } else {
584             return "'" . $this->escapeSimple($in) . "'";
585         }
586     }
587
588     // }}}
589     // {{{ fbsqlRaiseError()
590
591     /**
592      * Produces a DB_Error object regarding the current problem
593      *
594      * @param int $errno  if the error is being manually raised pass a
595      *                     DB_ERROR* constant here.  If this isn't passed
596      *                     the error information gathered from the DBMS.
597      *
598      * @return object  the DB_Error object
599      *
600      * @see DB_common::raiseError(),
601      *      DB_fbsql::errorNative(), DB_common::errorCode()
602      */
603     function fbsqlRaiseError($errno = null)
604     {
605         if ($errno === null) {
606             $errno = $this->errorCode(fbsql_errno($this->connection));
607         }
608         return $this->raiseError($errno, null, null, null,
609                                  @fbsql_error($this->connection));
610     }
611
612     // }}}
613     // {{{ errorNative()
614
615     /**
616      * Gets the DBMS' native error code produced by the last query
617      *
618      * @return int  the DBMS' error code
619      */
620     function errorNative()
621     {
622         return @fbsql_errno($this->connection);
623     }
624
625     // }}}
626     // {{{ tableInfo()
627
628     /**
629      * Returns information about a table or a result set
630      *
631      * @param object|string  $result  DB_result object from a query or a
632      *                                 string containing the name of a table.
633      *                                 While this also accepts a query result
634      *                                 resource identifier, this behavior is
635      *                                 deprecated.
636      * @param int            $mode    a valid tableInfo mode
637      *
638      * @return array  an associative array with the information requested.
639      *                 A DB_Error object on failure.
640      *
641      * @see DB_common::tableInfo()
642      */
643     function tableInfo($result, $mode = null)
644     {
645         if (is_string($result)) {
646             /*
647              * Probably received a table name.
648              * Create a result resource identifier.
649              */
650             $id = @fbsql_list_fields($this->dsn['database'],
651                                      $result, $this->connection);
652             $got_string = true;
653         } elseif (isset($result->result)) {
654             /*
655              * Probably received a result object.
656              * Extract the result resource identifier.
657              */
658             $id = $result->result;
659             $got_string = false;
660         } else {
661             /*
662              * Probably received a result resource identifier.
663              * Copy it.
664              * Deprecated.  Here for compatibility only.
665              */
666             $id = $result;
667             $got_string = false;
668         }
669
670         if (!is_resource($id)) {
671             return $this->fbsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
672         }
673
674         if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
675             $case_func = 'strtolower';
676         } else {
677             $case_func = 'strval';
678         }
679
680         $count = @fbsql_num_fields($id);
681         $res   = array();
682
683         if ($mode) {
684             $res['num_fields'] = $count;
685         }
686
687         for ($i = 0; $i < $count; $i++) {
688             $res[$i] = array(
689                 'table' => $case_func(@fbsql_field_table($id, $i)),
690                 'name'  => $case_func(@fbsql_field_name($id, $i)),
691                 'type'  => @fbsql_field_type($id, $i),
692                 'len'   => @fbsql_field_len($id, $i),
693                 'flags' => @fbsql_field_flags($id, $i),
694             );
695             if ($mode & DB_TABLEINFO_ORDER) {
696                 $res['order'][$res[$i]['name']] = $i;
697             }
698             if ($mode & DB_TABLEINFO_ORDERTABLE) {
699                 $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
700             }
701         }
702
703         // free the result only if we were called on a table
704         if ($got_string) {
705             @fbsql_free_result($id);
706         }
707         return $res;
708     }
709
710     // }}}
711     // {{{ getSpecialQuery()
712
713     /**
714      * Obtains the query string needed for listing a given type of objects
715      *
716      * @param string $type  the kind of objects you want to retrieve
717      *
718      * @return string  the SQL query string or null if the driver doesn't
719      *                  support the object type requested
720      *
721      * @access protected
722      * @see DB_common::getListOf()
723      */
724     function getSpecialQuery($type)
725     {
726         switch ($type) {
727             case 'tables':
728                 return 'SELECT "table_name" FROM information_schema.tables'
729                        . ' t0, information_schema.schemata t1'
730                        . ' WHERE t0.schema_pk=t1.schema_pk AND'
731                        . ' "table_type" = \'BASE TABLE\''
732                        . ' AND "schema_name" = current_schema';
733             case 'views':
734                 return 'SELECT "table_name" FROM information_schema.tables'
735                        . ' t0, information_schema.schemata t1'
736                        . ' WHERE t0.schema_pk=t1.schema_pk AND'
737                        . ' "table_type" = \'VIEW\''
738                        . ' AND "schema_name" = current_schema';
739             case 'users':
740                 return 'SELECT "user_name" from information_schema.users'; 
741             case 'functions':
742                 return 'SELECT "routine_name" FROM'
743                        . ' information_schema.psm_routines'
744                        . ' t0, information_schema.schemata t1'
745                        . ' WHERE t0.schema_pk=t1.schema_pk'
746                        . ' AND "routine_kind"=\'FUNCTION\''
747                        . ' AND "schema_name" = current_schema';
748             case 'procedures':
749                 return 'SELECT "routine_name" FROM'
750                        . ' information_schema.psm_routines'
751                        . ' t0, information_schema.schemata t1'
752                        . ' WHERE t0.schema_pk=t1.schema_pk'
753                        . ' AND "routine_kind"=\'PROCEDURE\''
754                        . ' AND "schema_name" = current_schema';
755             default:
756                 return null;
757         }
758     }
759
760     // }}}
761 }
762
763 /*
764  * Local variables:
765  * tab-width: 4
766  * c-basic-offset: 4
767  * End:
768  */
769
770 ?>