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