]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/pear/DB/sybase.php
Harmonize file footer
[SourceForge/phpwiki.git] / lib / pear / DB / sybase.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
3 // +----------------------------------------------------------------------+
4 // | PHP Version 4                                                        |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2004 The PHP Group                                |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.02 of the PHP license,      |
9 // | that is bundled with this package in the file LICENSE, and is        |
10 // | available at through the world-wide-web at                           |
11 // | http://www.php.net/license/2_02.txt.                                 |
12 // | If you did not receive a copy of the PHP license and are unable to   |
13 // | obtain it through the world-wide-web, please send a note to          |
14 // | license@php.net so we can mail you a copy immediately.               |
15 // +----------------------------------------------------------------------+
16 // | Authors: Sterling Hughes <sterling@php.net>                          |
17 // |          Antônio Carlos Venâncio Júnior <floripa@php.net>            |
18 // | Maintainer: Daniel Convissor <danielc@php.net>                       |
19 // +----------------------------------------------------------------------+
20 //
21 // $Id$
22
23
24 // TODO
25 //    - This driver may fail with multiple connections under the same
26 //      user/pass/host and different databases
27
28
29 require_once 'DB/common.php';
30
31 /**
32  * Database independent query interface definition for PHP's Sybase
33  * extension.
34  *
35  * @package  DB
36  * @version  $Id$
37  * @category Database
38  * @author   Sterling Hughes <sterling@php.net>
39  * @author   Antônio Carlos Venâncio Júnior <floripa@php.net>
40  */
41 class DB_sybase extends DB_common
42 {
43     // {{{ properties
44
45     var $connection;
46     var $phptype, $dbsyntax;
47     var $prepare_tokens = array();
48     var $prepare_types = array();
49     var $transaction_opcount = 0;
50     var $autocommit = true;
51
52     // }}}
53     // {{{ constructor
54
55     /**
56      * DB_sybase constructor.
57      *
58      * @access public
59      */
60     function DB_sybase()
61     {
62         $this->DB_common();
63         $this->phptype = 'sybase';
64         $this->dbsyntax = 'sybase';
65         $this->features = array(
66             'prepare' => false,
67             'pconnect' => true,
68             'transactions' => false,
69             'limit' => 'emulate'
70         );
71         $this->errorcode_map = array(
72         );
73     }
74
75     // }}}
76     // {{{ connect()
77
78     /**
79      * Connect to a database and log in as the specified user.
80      *
81      * @param $dsn the data source name (see DB::parseDSN for syntax)
82      * @param $persistent (optional) whether the connection should
83      *        be persistent
84      * @access public
85      * @return int DB_OK on success, a DB error on failure
86      */
87     function connect($dsninfo, $persistent = false)
88     {
89         if (!DB::assertExtension('sybase') &&
90             !DB::assertExtension('sybase_ct'))
91         {
92             return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
93         }
94
95         $this->dsn = $dsninfo;
96
97         $interface = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost';
98         $connect_function = $persistent ? 'sybase_pconnect' : 'sybase_connect';
99
100         if ($interface && $dsninfo['username'] && $dsninfo['password']) {
101             $conn = @$connect_function($interface, $dsninfo['username'],
102                                        $dsninfo['password']);
103         } elseif ($interface && $dsninfo['username']) {
104             /*
105              * Using false for pw as a workaround to avoid segfault.
106              * See PEAR bug 631
107              */
108             $conn = @$connect_function($interface, $dsninfo['username'],
109                                        false);
110         } else {
111             $conn = false;
112         }
113
114         if (!$conn) {
115             return $this->raiseError(DB_ERROR_CONNECT_FAILED);
116         }
117
118         if ($dsninfo['database']) {
119             if (!@sybase_select_db($dsninfo['database'], $conn)) {
120                 return $this->raiseError(DB_ERROR_NODBSELECTED, null,
121                                          null, null, @sybase_get_last_message());
122             }
123             $this->_db = $dsninfo['database'];
124         }
125
126         $this->connection = $conn;
127         return DB_OK;
128     }
129
130     // }}}
131     // {{{ disconnect()
132
133     /**
134      * Log out and disconnect from the database.
135      *
136      * @access public
137      *
138      * @return bool true on success, false if not connected.
139      */
140     function disconnect()
141     {
142         $ret = @sybase_close($this->connection);
143         $this->connection = null;
144         return $ret;
145     }
146
147     // }}}
148     // {{{ errorNative()
149
150     /**
151      * Get the last server error messge (if any)
152      *
153      * @return string sybase last error message
154      */
155     function errorNative()
156     {
157         return @sybase_get_last_message();
158     }
159
160     // }}}
161     // {{{ errorCode()
162
163     /**
164      * Determine PEAR::DB error code from the database's text error message.
165      *
166      * @param  string  $errormsg  error message returned from the database
167      * @return integer  an error number from a DB error constant
168      */
169     function errorCode($errormsg)
170     {
171         static $error_regexps;
172         if (!isset($error_regexps)) {
173             $error_regexps = array(
174                 '/Incorrect syntax near/'
175                     => DB_ERROR_SYNTAX,
176                 '/^Unclosed quote before the character string [\"\'].*[\"\']\./'
177                     => DB_ERROR_SYNTAX,
178                 '/Implicit conversion from datatype [\"\'].+[\"\'] to [\"\'].+[\"\'] is not allowed\./'
179                     => DB_ERROR_INVALID_NUMBER,
180                 '/Cannot drop the table [\"\'].+[\"\'], because it doesn\'t exist in the system catalogs\./'
181                     => DB_ERROR_NOSUCHTABLE,
182                 '/Only the owner of object [\"\'].+[\"\'] or a user with System Administrator \(SA\) role can run this command\./'
183                     => DB_ERROR_ACCESS_VIOLATION,
184                 '/^.+ permission denied on object .+, database .+, owner .+/'
185                     => DB_ERROR_ACCESS_VIOLATION,
186                 '/^.* permission denied, database .+, owner .+/'
187                     => DB_ERROR_ACCESS_VIOLATION,
188                 '/[^.*] not found\./'
189                     => DB_ERROR_NOSUCHTABLE,
190                 '/There is already an object named/'
191                     => DB_ERROR_ALREADY_EXISTS,
192                 '/Invalid column name/'
193                     => DB_ERROR_NOSUCHFIELD,
194                 '/does not allow null values/'
195                     => DB_ERROR_CONSTRAINT_NOT_NULL,
196                 '/Command has been aborted/'
197                     => DB_ERROR_CONSTRAINT,
198             );
199         }
200
201         foreach ($error_regexps as $regexp => $code) {
202             if (preg_match($regexp, $errormsg)) {
203                 return $code;
204             }
205         }
206         return DB_ERROR;
207     }
208
209     // }}}
210     // {{{ sybaseRaiseError()
211
212     /**
213      * Gather information about an error, then use that info to create a
214      * DB error object and finally return that object.
215      *
216      * @param  integer  $errno  PEAR error number (usually a DB constant) if
217      *                          manually raising an error
218      * @return object  DB error object
219      * @see errorNative()
220      * @see errorCode()
221      * @see DB_common::raiseError()
222      */
223     function sybaseRaiseError($errno = null)
224     {
225         $native = $this->errorNative();
226         if ($errno === null) {
227             $errno = $this->errorCode($native);
228         }
229         return $this->raiseError($errno, null, null, null, $native);
230     }
231
232     // }}}
233     // {{{ simpleQuery()
234
235     /**
236      * Send a query to Sybase and return the results as a Sybase resource
237      * identifier.
238      *
239      * @param the SQL query
240      *
241      * @access public
242      *
243      * @return mixed returns a valid Sybase result for successful SELECT
244      * queries, DB_OK for other successful queries.  A DB error is
245      * returned on failure.
246      */
247     function simpleQuery($query)
248     {
249         $ismanip = DB::isManip($query);
250         $this->last_query = $query;
251         if (!@sybase_select_db($this->_db, $this->connection)) {
252             return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
253         }
254         $query = $this->modifyQuery($query);
255         if (!$this->autocommit && $ismanip) {
256             if ($this->transaction_opcount == 0) {
257                 $result = @sybase_query('BEGIN TRANSACTION', $this->connection);
258                 if (!$result) {
259                     return $this->sybaseRaiseError();
260                 }
261             }
262             $this->transaction_opcount++;
263         }
264         $result = @sybase_query($query, $this->connection);
265         if (!$result) {
266             return $this->sybaseRaiseError();
267         }
268         if (is_resource($result)) {
269             $numrows = $this->numRows($result);
270             if (is_object($numrows)) {
271                 return $numrows;
272             }
273             $this->num_rows[(int)$result] = $numrows;
274             return $result;
275         }
276         // Determine which queries that should return data, and which
277         // should return an error code only.
278         return $ismanip ? DB_OK : $result;
279     }
280
281     // }}}
282     // {{{ nextResult()
283
284     /**
285      * Move the internal sybase result pointer to the next available result
286      *
287      * @param a valid sybase result resource
288      *
289      * @access public
290      *
291      * @return true if a result is available otherwise return false
292      */
293     function nextResult($result)
294     {
295         return false;
296     }
297
298     // }}}
299     // {{{ fetchInto()
300
301     /**
302      * Fetch a row and insert the data into an existing array.
303      *
304      * Formating of the array and the data therein are configurable.
305      * See DB_result::fetchInto() for more information.
306      *
307      * @param resource $result    query result identifier
308      * @param array    $arr       (reference) array where data from the row
309      *                            should be placed
310      * @param int      $fetchmode how the resulting array should be indexed
311      * @param int      $rownum    the row number to fetch
312      *
313      * @return mixed DB_OK on success, null when end of result set is
314      *               reached or on failure
315      *
316      * @see DB_result::fetchInto()
317      * @access private
318      */
319     function fetchInto($result, &$arr, $fetchmode, $rownum=null)
320     {
321         if ($rownum !== null) {
322             if (!@sybase_data_seek($result, $rownum)) {
323                 return null;
324             }
325         }
326         if ($fetchmode & DB_FETCHMODE_ASSOC) {
327             if (function_exists('sybase_fetch_assoc')) {
328                 $arr = @sybase_fetch_assoc($result);
329             } else {
330                 if ($arr = @sybase_fetch_array($result)) {
331                     foreach ($arr as $key => $value) {
332                         if (is_int($key)) {
333                             unset($arr[$key]);
334                         }
335                     }
336                 }
337             }
338             if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
339                 $arr = array_change_key_case($arr, CASE_LOWER);
340             }
341         } else {
342             $arr = @sybase_fetch_row($result);
343         }
344         if (!$arr) {
345             // reported not work as seems that sybase_get_last_message()
346             // always return a message here
347             //if ($errmsg = @sybase_get_last_message()) {
348             //    return $this->sybaseRaiseError($errmsg);
349             //} else {
350                 return null;
351             //}
352         }
353         if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
354             $this->_rtrimArrayValues($arr);
355         }
356         if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
357             $this->_convertNullArrayValuesToEmpty($arr);
358         }
359         return DB_OK;
360     }
361
362     // }}}
363     // {{{ freeResult()
364
365     /**
366      * Free the internal resources associated with $result.
367      *
368      * @param $result Sybase result identifier
369      *
370      * @access public
371      *
372      * @return bool true on success, false if $result is invalid
373      */
374     function freeResult($result)
375     {
376         unset($this->num_rows[(int)$result]);
377         return @sybase_free_result($result);
378     }
379
380     // }}}
381     // {{{ numCols()
382
383     /**
384      * Get the number of columns in a result set.
385      *
386      * @param $result Sybase result identifier
387      *
388      * @access public
389      *
390      * @return int the number of columns per row in $result
391      */
392     function numCols($result)
393     {
394         $cols = @sybase_num_fields($result);
395         if (!$cols) {
396             return $this->sybaseRaiseError();
397         }
398         return $cols;
399     }
400
401     // }}}
402     // {{{ numRows()
403
404     /**
405      * Get the number of rows in a result set.
406      *
407      * @param $result Sybase result identifier
408      *
409      * @access public
410      *
411      * @return int the number of rows in $result
412      */
413     function numRows($result)
414     {
415         $rows = @sybase_num_rows($result);
416         if ($rows === false) {
417             return $this->sybaseRaiseError();
418         }
419         return $rows;
420     }
421
422     // }}}
423     // {{{ affectedRows()
424
425     /**
426      * Gets the number of rows affected by the data manipulation
427      * query.  For other queries, this function returns 0.
428      *
429      * @return number of rows affected by the last query
430      */
431     function affectedRows()
432     {
433         if (DB::isManip($this->last_query)) {
434             $result = @sybase_affected_rows($this->connection);
435         } else {
436             $result = 0;
437         }
438         return $result;
439      }
440
441     // }}}
442     // {{{ nextId()
443
444     /**
445      * Returns the next free id in a sequence
446      *
447      * @param string  $seq_name  name of the sequence
448      * @param boolean $ondemand  when true, the seqence is automatically
449      *                           created if it does not exist
450      *
451      * @return int  the next id number in the sequence.  DB_Error if problem.
452      *
453      * @internal
454      * @see DB_common::nextID()
455      * @access public
456      */
457     function nextId($seq_name, $ondemand = true)
458     {
459         $seqname = $this->getSequenceName($seq_name);
460         if (!@sybase_select_db($this->_db, $this->connection)) {
461             return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
462         }
463         $repeat = 0;
464         do {
465             $this->pushErrorHandling(PEAR_ERROR_RETURN);
466             $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
467             $this->popErrorHandling();
468             if ($ondemand && DB::isError($result) &&
469                 ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE))
470             {
471                 $repeat = 1;
472                 $result = $this->createSequence($seq_name);
473                 if (DB::isError($result)) {
474                     return $this->raiseError($result);
475                 }
476             } elseif (!DB::isError($result)) {
477                 $result =& $this->query("SELECT @@IDENTITY FROM $seqname");
478                 $repeat = 0;
479             } else {
480                 $repeat = false;
481             }
482         } while ($repeat);
483         if (DB::isError($result)) {
484             return $this->raiseError($result);
485         }
486         $result = $result->fetchRow(DB_FETCHMODE_ORDERED);
487         return $result[0];
488     }
489
490     /**
491      * Creates a new sequence
492      *
493      * @param string $seq_name  name of the new sequence
494      *
495      * @return int  DB_OK on success.  A DB_Error object is returned if
496      *              problems arise.
497      *
498      * @internal
499      * @see DB_common::createSequence()
500      * @access public
501      */
502     function createSequence($seq_name)
503     {
504         $seqname = $this->getSequenceName($seq_name);
505         return $this->query("CREATE TABLE $seqname ".
506                             '(id numeric(10,0) IDENTITY NOT NULL ,' .
507                             'vapor int NULL)');
508     }
509
510     // }}}
511     // {{{ dropSequence()
512
513     /**
514      * Deletes a sequence
515      *
516      * @param string $seq_name  name of the sequence to be deleted
517      *
518      * @return int  DB_OK on success.  DB_Error if problems.
519      *
520      * @internal
521      * @see DB_common::dropSequence()
522      * @access public
523      */
524     function dropSequence($seq_name)
525     {
526         $seqname = $this->getSequenceName($seq_name);
527         return $this->query("DROP TABLE $seqname");
528     }
529
530     // }}}
531     // {{{ getSpecialQuery()
532
533     /**
534      * Returns the query needed to get some backend info
535      * @param string $type What kind of info you want to retrieve
536      * @return string The SQL query string
537      */
538     function getSpecialQuery($type)
539     {
540         switch ($type) {
541             case 'tables':
542                 return "select name from sysobjects where type = 'U' order by name";
543             case 'views':
544                 return "select name from sysobjects where type = 'V'";
545             default:
546                 return null;
547         }
548     }
549
550     // }}}
551     // {{{ autoCommit()
552
553     /**
554      * Enable/disable automatic commits
555      */
556     function autoCommit($onoff = false)
557     {
558         // XXX if $this->transaction_opcount > 0, we should probably
559         // issue a warning here.
560         $this->autocommit = $onoff ? true : false;
561         return DB_OK;
562     }
563
564     // }}}
565     // {{{ commit()
566
567     /**
568      * Commit the current transaction.
569      */
570     function commit()
571     {
572         if ($this->transaction_opcount > 0) {
573             if (!@sybase_select_db($this->_db, $this->connection)) {
574                 return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
575             }
576             $result = @sybase_query('COMMIT', $this->connection);
577             $this->transaction_opcount = 0;
578             if (!$result) {
579                 return $this->sybaseRaiseError();
580             }
581         }
582         return DB_OK;
583     }
584
585     // }}}
586     // {{{ rollback()
587
588     /**
589      * Roll back (undo) the current transaction.
590      */
591     function rollback()
592     {
593         if ($this->transaction_opcount > 0) {
594             if (!@sybase_select_db($this->_db, $this->connection)) {
595                 return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
596             }
597             $result = @sybase_query('ROLLBACK', $this->connection);
598             $this->transaction_opcount = 0;
599             if (!$result) {
600                 return $this->sybaseRaiseError();
601             }
602         }
603         return DB_OK;
604     }
605
606     // }}}
607     // {{{ tableInfo()
608
609     /**
610      * Returns information about a table or a result set.
611      *
612      * NOTE: only supports 'table' and 'flags' if <var>$result</var>
613      * is a table name.
614      *
615      * @param object|string  $result  DB_result object from a query or a
616      *                                string containing the name of a table
617      * @param int            $mode    a valid tableInfo mode
618      * @return array  an associative array with the information requested
619      *                or an error object if something is wrong
620      * @access public
621      * @internal
622      * @since 1.6.0
623      * @see DB_common::tableInfo()
624      */
625     function tableInfo($result, $mode = null)
626     {
627         if (isset($result->result)) {
628             /*
629              * Probably received a result object.
630              * Extract the result resource identifier.
631              */
632             $id = $result->result;
633             $got_string = false;
634         } elseif (is_string($result)) {
635             /*
636              * Probably received a table name.
637              * Create a result resource identifier.
638              */
639             if (!@sybase_select_db($this->_db, $this->connection)) {
640                 return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
641             }
642             $id = @sybase_query("SELECT * FROM $result WHERE 1=0",
643                                 $this->connection);
644             $got_string = true;
645         } else {
646             /*
647              * Probably received a result resource identifier.
648              * Copy it.
649              * Depricated.  Here for compatibility only.
650              */
651             $id = $result;
652             $got_string = false;
653         }
654
655         if (!is_resource($id)) {
656             return $this->sybaseRaiseError(DB_ERROR_NEED_MORE_DATA);
657         }
658
659         if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
660             $case_func = 'strtolower';
661         } else {
662             $case_func = 'strval';
663         }
664
665         $count = @sybase_num_fields($id);
666
667         // made this IF due to performance (one if is faster than $count if's)
668         if (!$mode) {
669
670             for ($i=0; $i<$count; $i++) {
671                 $f = @sybase_fetch_field($id, $i);
672
673                 // column_source is often blank
674                 if ($got_string) {
675                     $res[$i]['table'] = $case_func($result);
676                 } else {
677                     $res[$i]['table'] = $case_func($f->column_source);
678                 }
679                 $res[$i]['name']  = $case_func($f->name);
680                 $res[$i]['type']  = $f->type;
681                 $res[$i]['len']   = $f->max_length;
682                 if ($res[$i]['table']) {
683                     $res[$i]['flags'] = $this->_sybase_field_flags(
684                             $res[$i]['table'], $res[$i]['name']);
685                 } else {
686                     $res[$i]['flags'] = '';
687                 }
688             }
689
690         } else {
691             // get full info
692
693             $res['num_fields'] = $count;
694
695             for ($i=0; $i<$count; $i++) {
696                 $f = @sybase_fetch_field($id, $i);
697
698                 // column_source is often blank
699                 if ($got_string) {
700                     $res[$i]['table'] = $case_func($result);
701                 } else {
702                     $res[$i]['table'] = $case_func($f->column_source);
703                 }
704                 $res[$i]['name']  = $case_func($f->name);
705                 $res[$i]['type']  = $f->type;
706                 $res[$i]['len']   = $f->max_length;
707                 if ($res[$i]['table']) {
708                     $res[$i]['flags'] = $this->_sybase_field_flags(
709                             $res[$i]['table'], $res[$i]['name']);
710                 } else {
711                     $res[$i]['flags'] = '';
712                 }
713
714                 if ($mode & DB_TABLEINFO_ORDER) {
715                     $res['order'][$res[$i]['name']] = $i;
716                 }
717                 if ($mode & DB_TABLEINFO_ORDERTABLE) {
718                     $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
719                 }
720             }
721         }
722
723         // free the result only if we were called on a table
724         if ($got_string) {
725             @sybase_free_result($id);
726         }
727         return $res;
728     }
729
730     // }}}
731     // {{{ _sybase_field_flags()
732
733     /**
734      * Get the flags for a field.
735      *
736      * Currently supports:
737      *  + <samp>unique_key</samp>    (unique index, unique check or primary_key)
738      *  + <samp>multiple_key</samp>  (multi-key index)
739      *
740      * @param string  $table   table name
741      * @param string  $column  field name
742      * @return string  space delimited string of flags.  Empty string if none.
743      * @access private
744      */
745     function _sybase_field_flags($table, $column)
746     {
747         static $tableName = null;
748         static $flags = array();
749
750         if ($table != $tableName) {
751             $flags = array();
752             $tableName = $table;
753
754             // get unique/primary keys
755             $res = $this->getAll("sp_helpindex $table", DB_FETCHMODE_ASSOC);
756
757             if (!isset($res[0]['index_description'])) {
758                 return '';
759             }
760
761             foreach ($res as $val) {
762                 $keys = explode(', ', trim($val['index_keys']));
763
764                 if (sizeof($keys) > 1) {
765                     foreach ($keys as $key) {
766                         $this->_add_flag($flags[$key], 'multiple_key');
767                     }
768                 }
769
770                 if (strpos($val['index_description'], 'unique')) {
771                     foreach ($keys as $key) {
772                         $this->_add_flag($flags[$key], 'unique_key');
773                     }
774                 }
775             }
776
777         }
778
779         if (array_key_exists($column, $flags)) {
780             return(implode(' ', $flags[$column]));
781         }
782
783         return '';
784     }
785
786     // }}}
787     // {{{ _add_flag()
788
789     /**
790      * Adds a string to the flags array if the flag is not yet in there
791      * - if there is no flag present the array is created.
792      *
793      * @param array  $array  reference of flags array to add a value to
794      * @param mixed  $value  value to add to the flag array
795      * @access private
796      */
797     function _add_flag(&$array, $value)
798     {
799         if (!is_array($array)) {
800             $array = array($value);
801         } elseif (!in_array($value, $array)) {
802             array_push($array, $value);
803         }
804     }
805
806     // }}}
807     // {{{ quoteIdentifier()
808
809     /**
810      * Quote a string so it can be safely used as a table / column name
811      *
812      * Quoting style depends on which database driver is being used.
813      *
814      * @param string $str  identifier name to be quoted
815      *
816      * @return string  quoted identifier string
817      *
818      * @since 1.6.0
819      * @access public
820      */
821     function quoteIdentifier($str)
822     {
823         return '[' . str_replace(']', ']]', $str) . ']';
824     }
825
826     // }}}
827
828 }
829
830 /*
831  * Local variables:
832  * tab-width: 8
833  * c-basic-offset: 4
834  * End:
835  */
836
837 ?>