]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/pear/DB/odbc.php
Remove svn:keywords
[SourceForge/phpwiki.git] / lib / pear / DB / odbc.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 // | Author: Stig Bakken <ssb@php.net>                                    |
17 // | Maintainer: Daniel Convissor <danielc@php.net>                       |
18 // +----------------------------------------------------------------------+
19
20 // XXX legend:
21 //  More info on ODBC errors could be found here:
22 //  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/trblsql/tr_err_odbc_5stz.asp
23 //
24 // XXX ERRORMSG: The error message from the odbc function should
25 //                 be registered here.
26
27 require_once 'DB/common.php';
28
29 /**
30  * Database independent query interface definition for PHP's ODBC
31  * extension.
32  *
33  * @package  DB
34  * @version
35  * @category Database
36  * @author   Stig Bakken <ssb@php.net>
37  */
38 class DB_odbc extends DB_common
39 {
40     // {{{ properties
41
42     var $connection;
43     var $phptype, $dbsyntax;
44     var $row = array();
45
46     // }}}
47     // {{{ constructor
48
49     function DB_odbc()
50     {
51         $this->DB_common();
52         $this->phptype = 'odbc';
53         $this->dbsyntax = 'sql92';
54         $this->features = array(
55             'prepare' => true,
56             'pconnect' => true,
57             'transactions' => false,
58             'limit' => 'emulate'
59         );
60         $this->errorcode_map = array(
61             '01004' => DB_ERROR_TRUNCATED,
62             '07001' => DB_ERROR_MISMATCH,
63             '21S01' => DB_ERROR_MISMATCH,
64             '21S02' => DB_ERROR_MISMATCH,
65             '22003' => DB_ERROR_INVALID_NUMBER,
66             '22005' => DB_ERROR_INVALID_NUMBER,
67             '22008' => DB_ERROR_INVALID_DATE,
68             '22012' => DB_ERROR_DIVZERO,
69             '23000' => DB_ERROR_CONSTRAINT,
70             '23502' => DB_ERROR_CONSTRAINT_NOT_NULL,
71             '23503' => DB_ERROR_CONSTRAINT,
72             '23505' => DB_ERROR_CONSTRAINT,
73             '24000' => DB_ERROR_INVALID,
74             '34000' => DB_ERROR_INVALID,
75             '37000' => DB_ERROR_SYNTAX,
76             '42000' => DB_ERROR_SYNTAX,
77             '42601' => DB_ERROR_SYNTAX,
78             'IM001' => DB_ERROR_UNSUPPORTED,
79             'S0000' => DB_ERROR_NOSUCHTABLE,
80             'S0001' => DB_ERROR_ALREADY_EXISTS,
81             'S0002' => DB_ERROR_NOSUCHTABLE,
82             'S0011' => DB_ERROR_ALREADY_EXISTS,
83             'S0012' => DB_ERROR_NOT_FOUND,
84             'S0021' => DB_ERROR_ALREADY_EXISTS,
85             'S0022' => DB_ERROR_NOSUCHFIELD,
86             'S1000' => DB_ERROR_CONSTRAINT_NOT_NULL,
87             'S1009' => DB_ERROR_INVALID,
88             'S1090' => DB_ERROR_INVALID,
89             'S1C00' => DB_ERROR_NOT_CAPABLE
90         );
91     }
92
93     // }}}
94     // {{{ connect()
95
96     /**
97      * Connect to a database and log in as the specified user.
98      *
99      * @param $dsn the data source name (see DB::parseDSN for syntax)
100      * @param $persistent (optional) whether the connection should
101      *        be persistent
102      *
103      * @return int DB_OK on success, a DB error code on failure
104      */
105     function connect($dsninfo, $persistent = false)
106     {
107         if (!DB::assertExtension('odbc')) {
108             return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
109         }
110
111         $this->dsn = $dsninfo;
112         if ($dsninfo['dbsyntax']) {
113             $this->dbsyntax = $dsninfo['dbsyntax'];
114         }
115         switch ($this->dbsyntax) {
116             case 'solid':
117                 $this->features = array(
118                     'prepare' => true,
119                     'pconnect' => true,
120                     'transactions' => true
121                 );
122                 break;
123             case 'navision':
124                 // the Navision driver doesn't support fetch row by number
125                 $this->features['limit'] = false;
126         }
127
128         /*
129          * This is hear for backwards compatibility.
130          * Should have been using 'database' all along, but used hostspec.
131          */
132         if ($dsninfo['database']) {
133             $odbcdsn = $dsninfo['database'];
134         } elseif ($dsninfo['hostspec']) {
135             $odbcdsn = $dsninfo['hostspec'];
136         } else {
137             $odbcdsn = 'localhost';
138         }
139
140         if ($this->provides('pconnect')) {
141             $connect_function = $persistent ? 'odbc_pconnect' : 'odbc_connect';
142         } else {
143             $connect_function = 'odbc_connect';
144         }
145         $conn = @$connect_function($odbcdsn, $dsninfo['username'],
146                                    $dsninfo['password']);
147         if (!is_resource($conn)) {
148             return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null,
149                                          null, $this->errorNative());
150         }
151         $this->connection = $conn;
152         return DB_OK;
153     }
154
155     // }}}
156     // {{{ disconnect()
157
158     function disconnect()
159     {
160         $err = @odbc_close($this->connection);
161         $this->connection = null;
162         return $err;
163     }
164
165     // }}}
166     // {{{ simpleQuery()
167
168     /**
169      * Send a query to ODBC and return the results as a ODBC resource
170      * identifier.
171      *
172      * @param $query the SQL query
173      *
174      * @return int returns a valid ODBC result for successful SELECT
175      * queries, DB_OK for other successful queries.  A DB error code
176      * is returned on failure.
177      */
178     function simpleQuery($query)
179     {
180         $this->last_query = $query;
181         $query = $this->modifyQuery($query);
182         $result = @odbc_exec($this->connection, $query);
183         if (!$result) {
184             return $this->odbcRaiseError(); // XXX ERRORMSG
185         }
186         // Determine which queries that should return data, and which
187         // should return an error code only.
188         if (DB::isManip($query)) {
189             $this->manip_result = $result; // For affectedRows()
190             return DB_OK;
191         }
192         $this->row[(int)$result] = 0;
193         $this->manip_result = 0;
194         return $result;
195     }
196
197     // }}}
198     // {{{ nextResult()
199
200     /**
201      * Move the internal odbc result pointer to the next available result
202      *
203      * @param a valid fbsql result resource
204      *
205      * @access public
206      *
207      * @return true if a result is available otherwise return false
208      */
209     function nextResult($result)
210     {
211         return @odbc_next_result($result);
212     }
213
214     // }}}
215     // {{{ fetchInto()
216
217     /**
218      * Fetch a row and insert the data into an existing array.
219      *
220      * Formating of the array and the data therein are configurable.
221      * See DB_result::fetchInto() for more information.
222      *
223      * @param resource $result query result identifier
224      * @param array    $arr    (reference) array where data from the row
225      *                            should be placed
226      * @param int $fetchmode how the resulting array should be indexed
227      * @param int $rownum    the row number to fetch
228      *
229      * @return mixed DB_OK on success, null when end of result set is
230      *               reached or on failure
231      *
232      * @see DB_result::fetchInto()
233      * @access private
234      */
235     function fetchInto($result, &$arr, $fetchmode, $rownum=null)
236     {
237         $arr = array();
238         if ($rownum !== null) {
239             $rownum++; // ODBC first row is 1
240             if (version_compare(phpversion(), '4.2.0', 'ge')) {
241                 $cols = @odbc_fetch_into($result, $arr, $rownum);
242             } else {
243                 $cols = @odbc_fetch_into($result, $rownum, $arr);
244             }
245         } else {
246             $cols = @odbc_fetch_into($result, $arr);
247         }
248
249         if (!$cols) {
250             /* XXX FIXME: doesn't work with unixODBC and easysoft
251                           (get corrupted $errno values)
252             if ($errno = @odbc_error($this->connection)) {
253                 return $this->RaiseError($errno);
254             }*/
255             return null;
256         }
257         if ($fetchmode !== DB_FETCHMODE_ORDERED) {
258             for ($i = 0; $i < count($arr); $i++) {
259                 $colName = @odbc_field_name($result, $i+1);
260                 $a[$colName] = $arr[$i];
261             }
262             if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
263                 $a = array_change_key_case($a, CASE_LOWER);
264             }
265             $arr = $a;
266         }
267         if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
268             $this->_rtrimArrayValues($arr);
269         }
270         if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
271             $this->_convertNullArrayValuesToEmpty($arr);
272         }
273         return DB_OK;
274     }
275
276     // }}}
277     // {{{ freeResult()
278
279     function freeResult($result)
280     {
281         unset($this->row[(int)$result]);
282         return @odbc_free_result($result);
283     }
284
285     // }}}
286     // {{{ numCols()
287
288     function numCols($result)
289     {
290         $cols = @odbc_num_fields($result);
291         if (!$cols) {
292             return $this->odbcRaiseError();
293         }
294         return $cols;
295     }
296
297     // }}}
298     // {{{ affectedRows()
299
300     /**
301      * Returns the number of rows affected by a manipulative query
302      * (INSERT, DELETE, UPDATE)
303      * @return mixed int affected rows, 0 when non manip queries or
304      *               DB error on error
305      */
306     function affectedRows()
307     {
308         if (empty($this->manip_result)) {  // In case of SELECT stms
309             return 0;
310         }
311         $nrows = @odbc_num_rows($this->manip_result);
312         if ($nrows == -1) {
313             return $this->odbcRaiseError();
314         }
315         return $nrows;
316     }
317
318     // }}}
319     // {{{ numRows()
320
321     /**
322      * ODBC may or may not support counting rows in the result set of
323      * SELECTs.
324      *
325      * @param $result the odbc result resource
326      * @return the number of rows, or 0
327      */
328     function numRows($result)
329     {
330         $nrows = @odbc_num_rows($result);
331         if ($nrows == -1) {
332             return $this->odbcRaiseError(DB_ERROR_UNSUPPORTED);
333         }
334         return $nrows;
335     }
336
337     // }}}
338     // {{{ quoteIdentifier()
339
340     /**
341      * Quote a string so it can be safely used as a table / column name
342      *
343      * Quoting style depends on which dbsyntax was passed in the DSN.
344      *
345      * Use 'mssql' as the dbsyntax in the DB DSN only if you've unchecked
346      * "Use ANSI quoted identifiers" when setting up the ODBC data source.
347      *
348      * @param string $str identifier name to be quoted
349      *
350      * @return string quoted identifier string
351      *
352      * @since 1.6.0
353      * @access public
354      */
355     function quoteIdentifier($str)
356     {
357         switch ($this->dsn['dbsyntax']) {
358             case 'access':
359                 return '[' . $str . ']';
360             case 'mssql':
361             case 'sybase':
362                 return '[' . str_replace(']', ']]', $str) . ']';
363             case 'mysql':
364             case 'mysqli':
365                 return '`' . $str . '`';
366             default:
367                 return '"' . str_replace('"', '""', $str) . '"';
368         }
369     }
370
371     // }}}
372     // {{{ quote()
373
374     /**
375      * @deprecated  Deprecated in release 1.6.0
376      * @internal
377      */
378     function quote($str) {
379         return $this->quoteSmart($str);
380     }
381
382     // }}}
383     // {{{ errorNative()
384
385     /**
386      * Get the native error code of the last error (if any) that
387      * occured on the current connection.
388      *
389      * @access public
390      *
391      * @return int ODBC error code
392      */
393     function errorNative()
394     {
395         if (!isset($this->connection) || !is_resource($this->connection)) {
396             return @odbc_error() . ' ' . @odbc_errormsg();
397         }
398         return @odbc_error($this->connection) . ' ' . @odbc_errormsg($this->connection);
399     }
400
401     // }}}
402     // {{{ nextId()
403
404     /**
405      * Returns the next free id in a sequence
406      *
407      * @param string  $seq_name name of the sequence
408      * @param boolean $ondemand when true, the seqence is automatically
409      *                           created if it does not exist
410      *
411      * @return int the next id number in the sequence.  DB_Error if problem.
412      *
413      * @internal
414      * @see DB_common::nextID()
415      * @access public
416      */
417     function nextId($seq_name, $ondemand = true)
418     {
419         $seqname = $this->getSequenceName($seq_name);
420         $repeat = 0;
421         do {
422             $this->pushErrorHandling(PEAR_ERROR_RETURN);
423             $result = $this->query("update ${seqname} set id = id + 1");
424             $this->popErrorHandling();
425             if ($ondemand && DB::isError($result) &&
426                 $result->getCode() == DB_ERROR_NOSUCHTABLE) {
427                 $repeat = 1;
428                 $this->pushErrorHandling(PEAR_ERROR_RETURN);
429                 $result = $this->createSequence($seq_name);
430                 $this->popErrorHandling();
431                 if (DB::isError($result)) {
432                     return $this->raiseError($result);
433                 }
434                 $result = $this->query("insert into ${seqname} (id) values(0)");
435             } else {
436                 $repeat = 0;
437             }
438         } while ($repeat);
439
440         if (DB::isError($result)) {
441             return $this->raiseError($result);
442         }
443
444         $result = $this->query("select id from ${seqname}");
445         if (DB::isError($result)) {
446             return $result;
447         }
448
449         $row = $result->fetchRow(DB_FETCHMODE_ORDERED);
450         if (DB::isError($row || !$row)) {
451             return $row;
452         }
453
454         return $row[0];
455     }
456
457     /**
458      * Creates a new sequence
459      *
460      * @param string $seq_name name of the new sequence
461      *
462      * @return int DB_OK on success.  A DB_Error object is returned if
463      *              problems arise.
464      *
465      * @internal
466      * @see DB_common::createSequence()
467      * @access public
468      */
469     function createSequence($seq_name)
470     {
471         $seqname = $this->getSequenceName($seq_name);
472         return $this->query("CREATE TABLE ${seqname} ".
473                             '(id integer NOT NULL,'.
474                             ' PRIMARY KEY(id))');
475     }
476
477     // }}}
478     // {{{ dropSequence()
479
480     /**
481      * Deletes a sequence
482      *
483      * @param string $seq_name name of the sequence to be deleted
484      *
485      * @return int DB_OK on success.  DB_Error if problems.
486      *
487      * @internal
488      * @see DB_common::dropSequence()
489      * @access public
490      */
491     function dropSequence($seq_name)
492     {
493         $seqname = $this->getSequenceName($seq_name);
494         return $this->query("DROP TABLE ${seqname}");
495     }
496
497     // }}}
498     // {{{ autoCommit()
499
500     function autoCommit($onoff = false)
501     {
502         if (!@odbc_autocommit($this->connection, $onoff)) {
503             return $this->odbcRaiseError();
504         }
505         return DB_OK;
506     }
507
508     // }}}
509     // {{{ commit()
510
511     function commit()
512     {
513         if (!@odbc_commit($this->connection)) {
514             return $this->odbcRaiseError();
515         }
516         return DB_OK;
517     }
518
519     // }}}
520     // {{{ rollback()
521
522     function rollback()
523     {
524         if (!@odbc_rollback($this->connection)) {
525             return $this->odbcRaiseError();
526         }
527         return DB_OK;
528     }
529
530     // }}}
531     // {{{ odbcRaiseError()
532
533     /**
534      * Gather information about an error, then use that info to create a
535      * DB error object and finally return that object.
536      *
537      * @param integer $errno PEAR error number (usually a DB constant) if
538      *                          manually raising an error
539      * @return object DB error object
540      * @see errorNative()
541      * @see DB_common::errorCode()
542      * @see DB_common::raiseError()
543      */
544     function odbcRaiseError($errno = null)
545     {
546         if ($errno === null) {
547             switch ($this->dbsyntax) {
548                 case 'access':
549                     if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
550                         $this->errorcode_map['07001'] = DB_ERROR_NOSUCHFIELD;
551                     } else {
552                         // Doing this in case mode changes during runtime.
553                         $this->errorcode_map['07001'] = DB_ERROR_MISMATCH;
554                     }
555             }
556             $errno = $this->errorCode(odbc_error($this->connection));
557         }
558         return $this->raiseError($errno, null, null, null,
559                         $this->errorNative());
560     }
561
562     // }}}
563
564 }
565
566 /*
567  * Local variables:
568  * tab-width: 4
569  * c-basic-offset: 4
570  * End:
571  */
572
573 ?>