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