svncommit
2008-09-18 d0b973cf6aed4a7cb705f706624d25b31d19ed52
commit | author | age
95ebbc 1 <?php
T 2 // vim: set et ts=4 sw=4 fdm=marker:
3 // +----------------------------------------------------------------------+
4 // | PHP versions 4 and 5                                                 |
5 // +----------------------------------------------------------------------+
d1403f 6 // | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
95ebbc 7 // | Stig. S. Bakken, Lukas Smith, Frank M. Kromann                       |
T 8 // | All rights reserved.                                                 |
9 // +----------------------------------------------------------------------+
10 // | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
11 // | API as well as database abstraction for PHP applications.            |
12 // | This LICENSE is in the BSD license style.                            |
13 // |                                                                      |
14 // | Redistribution and use in source and binary forms, with or without   |
15 // | modification, are permitted provided that the following conditions   |
16 // | are met:                                                             |
17 // |                                                                      |
18 // | Redistributions of source code must retain the above copyright       |
19 // | notice, this list of conditions and the following disclaimer.        |
20 // |                                                                      |
21 // | Redistributions in binary form must reproduce the above copyright    |
22 // | notice, this list of conditions and the following disclaimer in the  |
23 // | documentation and/or other materials provided with the distribution. |
24 // |                                                                      |
25 // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
26 // | Lukas Smith nor the names of his contributors may be used to endorse |
27 // | or promote products derived from this software without specific prior|
28 // | written permission.                                                  |
29 // |                                                                      |
30 // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
31 // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
32 // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
33 // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
34 // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
35 // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
36 // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
37 // |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
38 // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
39 // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
40 // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
41 // | POSSIBILITY OF SUCH DAMAGE.                                          |
42 // +----------------------------------------------------------------------+
43 // | Author: Frank M. Kromann <frank@kromann.info>                        |
44 // +----------------------------------------------------------------------+
45 //
d1403f 46 // $Id: mssql.php,v 1.174 2008/03/08 14:18:39 quipo Exp $
95ebbc 47 //
T 48 // {{{ Class MDB2_Driver_mssql
49 /**
50  * MDB2 MSSQL Server driver
51  *
52  * @package MDB2
53  * @category Database
54  * @author  Frank M. Kromann <frank@kromann.info>
55  */
56 class MDB2_Driver_mssql extends MDB2_Driver_Common
57 {
58     // {{{ properties
59
60     var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false);
61
62     var $identifier_quoting = array('start' => '[', 'end' => ']', 'escape' => ']');
63
64     // }}}
65     // {{{ constructor
66
67     /**
68      * Constructor
69      */
70     function __construct()
71     {
72         parent::__construct();
73
74         $this->phptype = 'mssql';
75         $this->dbsyntax = 'mssql';
76
77         $this->supported['sequences'] = 'emulated';
78         $this->supported['indexes'] = true;
79         $this->supported['affected_rows'] = true;
80         $this->supported['transactions'] = true;
81         $this->supported['savepoints'] = false;
82         $this->supported['summary_functions'] = true;
83         $this->supported['order_by_text'] = true;
84         $this->supported['current_id'] = 'emulated';
85         $this->supported['limit_queries'] = 'emulated';
86         $this->supported['LOBs'] = true;
87         $this->supported['replace'] = 'emulated';
88         $this->supported['sub_selects'] = true;
d1403f 89         $this->supported['triggers'] = true;
95ebbc 90         $this->supported['auto_increment'] = true;
T 91         $this->supported['primary_key'] = true;
92         $this->supported['result_introspection'] = true;
93         $this->supported['prepared_statements'] = 'emulated';
94         $this->supported['pattern_escaping'] = true;
95         $this->supported['new_link'] = true;
96
d1403f 97         $this->options['DBA_username'] = false;
A 98         $this->options['DBA_password'] = false;
95ebbc 99         $this->options['database_device'] = false;
T 100         $this->options['database_size'] = false;
d1403f 101         $this->options['max_identifiers_length'] = 128; // MS Access: 64
95ebbc 102     }
T 103
104     // }}}
105     // {{{ errorInfo()
106
107     /**
108      * This method is used to collect information about an error
109      *
110      * @param integer $error
111      * @return array
112      * @access public
113      */
d1403f 114     function errorInfo($error = null, $connection = null)
95ebbc 115     {
d1403f 116         if (is_null($connection)) {
A 117             $connection = $this->connection;
118         }
119
95ebbc 120         $native_code = null;
d1403f 121         if ($connection) {
A 122             $result = @mssql_query('select @@ERROR as ErrorCode', $connection);
95ebbc 123             if ($result) {
T 124                 $native_code = @mssql_result($result, 0, 0);
125                 @mssql_free_result($result);
126             }
127         }
128         $native_msg = @mssql_get_last_message();
129         if (is_null($error)) {
130             static $ecode_map;
131             if (empty($ecode_map)) {
132                 $ecode_map = array(
133                     102   => MDB2_ERROR_SYNTAX,
134                     110   => MDB2_ERROR_VALUE_COUNT_ON_ROW,
135                     155   => MDB2_ERROR_NOSUCHFIELD,
136                     156   => MDB2_ERROR_SYNTAX,
137                     170   => MDB2_ERROR_SYNTAX,
138                     207   => MDB2_ERROR_NOSUCHFIELD,
139                     208   => MDB2_ERROR_NOSUCHTABLE,
140                     245   => MDB2_ERROR_INVALID_NUMBER,
141                     319   => MDB2_ERROR_SYNTAX,
142                     321   => MDB2_ERROR_NOSUCHFIELD,
143                     325   => MDB2_ERROR_SYNTAX,
144                     336   => MDB2_ERROR_SYNTAX,
145                     515   => MDB2_ERROR_CONSTRAINT_NOT_NULL,
146                     547   => MDB2_ERROR_CONSTRAINT,
d1403f 147                     911   => MDB2_ERROR_NOT_FOUND,
95ebbc 148                     1018  => MDB2_ERROR_SYNTAX,
T 149                     1035  => MDB2_ERROR_SYNTAX,
d1403f 150                     1801  => MDB2_ERROR_ALREADY_EXISTS,
95ebbc 151                     1913  => MDB2_ERROR_ALREADY_EXISTS,
T 152                     2209  => MDB2_ERROR_SYNTAX,
153                     2223  => MDB2_ERROR_SYNTAX,
154                     2248  => MDB2_ERROR_SYNTAX,
155                     2256  => MDB2_ERROR_SYNTAX,
156                     2257  => MDB2_ERROR_SYNTAX,
157                     2627  => MDB2_ERROR_CONSTRAINT,
158                     2714  => MDB2_ERROR_ALREADY_EXISTS,
159                     3607  => MDB2_ERROR_DIVZERO,
160                     3701  => MDB2_ERROR_NOSUCHTABLE,
161                     7630  => MDB2_ERROR_SYNTAX,
162                     8134  => MDB2_ERROR_DIVZERO,
163                     9303  => MDB2_ERROR_SYNTAX,
164                     9317  => MDB2_ERROR_SYNTAX,
165                     9318  => MDB2_ERROR_SYNTAX,
166                     9331  => MDB2_ERROR_SYNTAX,
167                     9332  => MDB2_ERROR_SYNTAX,
168                     15253 => MDB2_ERROR_SYNTAX,
169                 );
170             }
171             if (isset($ecode_map[$native_code])) {
172                 if ($native_code == 3701
173                     && preg_match('/Cannot drop the index/i', $native_msg)
174                 ) {
175                    $error = MDB2_ERROR_NOT_FOUND;
176                 } else {
177                     $error = $ecode_map[$native_code];
178                 }
179             }
180         }
181         return array($error, $native_code, $native_msg);
182     }
183
184     // }}}
185     // {{{ function escapePattern($text)
186
187     /**
188      * Quotes pattern (% and _) characters in a string)
189      *
190      * @param   string  the input string to quote
191      *
192      * @return  string  quoted string
193      *
194      * @access  public
195      */
196     function escapePattern($text)
197     {
198         $text = str_replace("[", "[ [ ]", $text);
199         foreach ($this->wildcards as $wildcard) {
200             $text = str_replace($wildcard, '[' . $wildcard . ']', $text);
201         }
202         return $text;
203     }
204
205     // }}}
206     // {{{ beginTransaction()
207
208     /**
209      * Start a transaction or set a savepoint.
210      *
211      * @param   string  name of a savepoint to set
212      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
213      *
214      * @access  public
215      */
216     function beginTransaction($savepoint = null)
217     {
218         $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
219         if (!is_null($savepoint)) {
220             if (!$this->in_transaction) {
221                 return $this->raiseError(MDB2_ERROR_INVALID, null, null,
222                     'savepoint cannot be released when changes are auto committed', __FUNCTION__);
223             }
224             $query = 'SAVE TRANSACTION '.$savepoint;
225             return $this->_doQuery($query, true);
226         } elseif ($this->in_transaction) {
227             return MDB2_OK;  //nothing to do
228         }
229         if (!$this->destructor_registered && $this->opened_persistent) {
230             $this->destructor_registered = true;
231             register_shutdown_function('MDB2_closeOpenTransactions');
232         }
233         $result =& $this->_doQuery('BEGIN TRANSACTION', true);
234         if (PEAR::isError($result)) {
235             return $result;
236         }
237         $this->in_transaction = true;
238         return MDB2_OK;
239     }
240
241     // }}}
242     // {{{ commit()
243
244     /**
245      * Commit the database changes done during a transaction that is in
246      * progress or release a savepoint. This function may only be called when
247      * auto-committing is disabled, otherwise it will fail. Therefore, a new
248      * transaction is implicitly started after committing the pending changes.
249      *
250      * @param   string  name of a savepoint to release
251      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
252      *
253      * @access  public
254      */
255     function commit($savepoint = null)
256     {
257         $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
258         if (!$this->in_transaction) {
259             return $this->raiseError(MDB2_ERROR_INVALID, null, null,
260                 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__);
261         }
262         if (!is_null($savepoint)) {
263             return MDB2_OK;
264         }
265
266         $result =& $this->_doQuery('COMMIT TRANSACTION', true);
267         if (PEAR::isError($result)) {
268             return $result;
269         }
270         $this->in_transaction = false;
271         return MDB2_OK;
272     }
273
274     // }}}
275     // {{{ rollback()
276
277     /**
278      * Cancel any database changes done during a transaction or since a specific
279      * savepoint that is in progress. This function may only be called when
280      * auto-committing is disabled, otherwise it will fail. Therefore, a new
281      * transaction is implicitly started after canceling the pending changes.
282      *
283      * @param   string  name of a savepoint to rollback to
284      * @return  mixed   MDB2_OK on success, a MDB2 error on failure
285      *
286      * @access  public
287      */
288     function rollback($savepoint = null)
289     {
290         $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
291         if (!$this->in_transaction) {
292             return $this->raiseError(MDB2_ERROR_INVALID, null, null,
293                 'rollback cannot be done changes are auto committed', __FUNCTION__);
294         }
295         if (!is_null($savepoint)) {
296             $query = 'ROLLBACK TRANSACTION '.$savepoint;
297             return $this->_doQuery($query, true);
298         }
299
300         $result =& $this->_doQuery('ROLLBACK TRANSACTION', true);
301         if (PEAR::isError($result)) {
302             return $result;
303         }
304         $this->in_transaction = false;
305         return MDB2_OK;
306     }
307
308     // }}}
d1403f 309     // {{{ _doConnect()
A 310
311     /**
312      * do the grunt work of the connect
313      *
314      * @return connection on success or MDB2 Error Object on failure
315      * @access protected
316      */
317     function _doConnect($username, $password, $persistent = false)
318     {
319         if (!PEAR::loadExtension($this->phptype) && !PEAR::loadExtension('sybase_ct')) {
320             return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
321                 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__);
322         }
323
324         $params = array(
325             $this->dsn['hostspec'] ? $this->dsn['hostspec'] : 'localhost',
326             $username ? $username : null,
327             $password ? $password : null,
328         );
329         if ($this->dsn['port']) {
330             $params[0].= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':').$this->dsn['port'];
331         }
332         if (!$persistent) {
333             if (isset($this->dsn['new_link'])
334                 && ($this->dsn['new_link'] == 'true' || $this->dsn['new_link'] === true)
335             ) {
336                 $params[] = true;
337             } else {
338                 $params[] = false;
339             }
340         }
341
342         $connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect';
343
344         $connection = @call_user_func_array($connect_function, $params);
345         if ($connection <= 0) {
346             return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
347                 'unable to establish a connection', __FUNCTION__, __FUNCTION__);
348         }
349
350         @mssql_query('SET ANSI_NULL_DFLT_ON ON', $connection);
351
352         if (!empty($this->dsn['charset'])) {
353             $result = $this->setCharset($this->dsn['charset'], $connection);
354             if (PEAR::isError($result)) {
355                 return $result;
356             }
357         }
358
359        if ((bool)ini_get('mssql.datetimeconvert')) {
360            @ini_set('mssql.datetimeconvert', '0');
361        }
362
363        if (empty($this->dsn['disable_iso_date'])) {
364            @mssql_query('SET DATEFORMAT ymd', $connection);
365        }
366
367        return $connection;
368     }
369
370     // }}}
95ebbc 371     // {{{ connect()
T 372
373     /**
374      * Connect to the database
375      *
376      * @return true on success, MDB2 Error Object on failure
377      */
378     function connect()
379     {
380         if (is_resource($this->connection)) {
381             //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0
382             if (MDB2::areEquals($this->connected_dsn, $this->dsn)
383                 && $this->opened_persistent == $this->options['persistent']
384             ) {
385                 return MDB2_OK;
386             }
387             $this->disconnect(false);
388         }
389
d1403f 390         $connection = $this->_doConnect(
A 391             $this->dsn['username'],
392             $this->dsn['password'],
393             $this->options['persistent']
95ebbc 394         );
d1403f 395         if (PEAR::isError($connection)) {
A 396             return $connection;
95ebbc 397         }
T 398
399         $this->connection = $connection;
400         $this->connected_dsn = $this->dsn;
401         $this->connected_database_name = '';
402         $this->opened_persistent = $this->options['persistent'];
403         $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
404
405         if ($this->database_name) {
406             if ($this->database_name != $this->connected_database_name) {
407                 if (!@mssql_select_db($this->database_name, $connection)) {
408                     $err = $this->raiseError(null, null, null,
409                         'Could not select the database: '.$this->database_name, __FUNCTION__);
410                     return $err;
411                 }
412                 $this->connected_database_name = $this->database_name;
413             }
414         }
415
416         return MDB2_OK;
d1403f 417     }
A 418
419     // }}}
420     // {{{ databaseExists()
421
422     /**
423      * check if given database name is exists?
424      *
425      * @param string $name    name of the database that should be checked
426      *
427      * @return mixed true/false on success, a MDB2 error on failure
428      * @access public
429      */
430     function databaseExists($name)
431     {
432         $connection = $this->_doConnect($this->dsn['username'],
433                                         $this->dsn['password'],
434                                         $this->options['persistent']);
435         if (PEAR::isError($connection)) {
436             return $connection;
437         }
438
439         $result = @mssql_select_db($name, $connection);
440         $errorInfo = $this->errorInfo(null, $connection);
441         @mssql_close($connection);
442         if (!$result) {
443             if ($errorInfo[0] != MDB2_ERROR_NOT_FOUND) {
444             exit;
445                 $result = $this->raiseError($errorInfo[0], null, null, $errorInfo[2], __FUNCTION__);
446                 return $result;
447             }
448             $result = false;
449         }
450
451         return $result;
95ebbc 452     }
T 453
454     // }}}
455     // {{{ disconnect()
456
457     /**
458      * Log out and disconnect from the database.
459      *
460      * @param  boolean $force if the disconnect should be forced even if the
461      *                        connection is opened persistently
462      * @return mixed true on success, false if not connected and error
463      *                object on error
464      * @access public
465      */
466     function disconnect($force = true)
467     {
468         if (is_resource($this->connection)) {
469             if ($this->in_transaction) {
470                 $dsn = $this->dsn;
471                 $database_name = $this->database_name;
472                 $persistent = $this->options['persistent'];
473                 $this->dsn = $this->connected_dsn;
474                 $this->database_name = $this->connected_database_name;
475                 $this->options['persistent'] = $this->opened_persistent;
476                 $this->rollback();
477                 $this->dsn = $dsn;
478                 $this->database_name = $database_name;
479                 $this->options['persistent'] = $persistent;
480             }
481
482             if (!$this->opened_persistent || $force) {
483                 @mssql_close($this->connection);
484             }
485         }
486         return parent::disconnect($force);
d1403f 487     }
A 488
489     // }}}
490     // {{{ standaloneQuery()
491
492    /**
493      * execute a query as DBA
494      *
495      * @param string $query the SQL query
496      * @param mixed   $types  array that contains the types of the columns in
497      *                        the result set
498      * @param boolean $is_manip  if the query is a manipulation query
499      * @return mixed MDB2_OK on success, a MDB2 error on failure
500      * @access public
501      */
502     function &standaloneQuery($query, $types = null, $is_manip = false)
503     {
504         $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username'];
505         $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password'];
506         $connection = $this->_doConnect($user, $pass, $this->options['persistent']);
507         if (PEAR::isError($connection)) {
508             return $connection;
509         }
510
511         $offset = $this->offset;
512         $limit = $this->limit;
513         $this->offset = $this->limit = 0;
514         $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
515         
516         $result =& $this->_doQuery($query, $is_manip, $connection, $this->database_name);
517         if (!PEAR::isError($result)) {
518             $result = $this->_affectedRows($connection, $result);
519         }
520
521         @mssql_close($connection);
522         return $result;
95ebbc 523     }
T 524
525     // }}}
526     // {{{ _doQuery()
527
528     /**
529      * Execute a query
530      * @param string $query  query
531      * @param boolean $is_manip  if the query is a manipulation query
532      * @param resource $connection
533      * @param string $database_name
534      * @return result or error object
535      * @access protected
536      */
537     function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null)
538     {
539         $this->last_query = $query;
540         $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
541         if ($result) {
542             if (PEAR::isError($result)) {
543                 return $result;
544             }
545             $query = $result;
546         }
547         if ($this->options['disable_query']) {
548             $result = $is_manip ? 0 : null;
549             return $result;
550         }
551
552         if (is_null($connection)) {
553             $connection = $this->getConnection();
554             if (PEAR::isError($connection)) {
555                 return $connection;
556             }
557         }
558         if (is_null($database_name)) {
559             $database_name = $this->database_name;
560         }
561
562         if ($database_name) {
563             if ($database_name != $this->connected_database_name) {
564                 if (!@mssql_select_db($database_name, $connection)) {
565                     $err = $this->raiseError(null, null, null,
566                         'Could not select the database: '.$database_name, __FUNCTION__);
567                     return $err;
568                 }
569                 $this->connected_database_name = $database_name;
570             }
571         }
572
573         $result = @mssql_query($query, $connection);
574         if (!$result) {
575             $err =& $this->raiseError(null, null, null,
576                 'Could not execute statement', __FUNCTION__);
577             return $err;
578         }
579
580         $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
581         return $result;
582     }
583
584     // }}}
585     // {{{ _affectedRows()
586
587     /**
588      * Returns the number of rows affected
589      *
590      * @param resource $result
591      * @param resource $connection
592      * @return mixed MDB2 Error Object or the number of rows affected
593      * @access private
594      */
595     function _affectedRows($connection, $result = null)
596     {
597         if (is_null($connection)) {
598             $connection = $this->getConnection();
599             if (PEAR::isError($connection)) {
600                 return $connection;
601             }
602         }
603         return @mssql_rows_affected($connection);
604     }
605
606     // }}}
607     // {{{ _modifyQuery()
608
609     /**
610      * Changes a query string for various DBMS specific reasons
611      *
612      * @param string $query  query to modify
613      * @param boolean $is_manip  if it is a DML query
614      * @param integer $limit  limit the number of rows
615      * @param integer $offset  start reading from given offset
616      * @return string modified query
617      * @access protected
618      */
619     function _modifyQuery($query, $is_manip, $limit, $offset)
620     {
621         if ($limit > 0) {
622             $fetch = $offset + $limit;
623             if (!$is_manip) {
624                 return preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',
625                     "\\1SELECT\\2 TOP $fetch", $query);
626             }
627         }
628         return $query;
629     }
630
631     // }}}
632     // {{{ getServerVersion()
633
634     /**
635      * return version information about the server
636      *
637      * @param bool   $native  determines if the raw version string should be returned
638      * @return mixed array/string with version information or MDB2 error object
639      * @access public
640      */
641     function getServerVersion($native = false)
642     {
643         if ($this->connected_server_info) {
644             $server_info = $this->connected_server_info;
645         } else {
646             $query = 'SELECT @@VERSION';
647             $server_info = $this->queryOne($query, 'text');
648             if (PEAR::isError($server_info)) {
649                 return $server_info;
650             }
651         }
652         // cache server_info
653         $this->connected_server_info = $server_info;
654         if (!$native && !PEAR::isError($server_info)) {
d1403f 655             if (preg_match('/(\d+)\.(\d+)\.(\d+)/', $server_info, $tmp)) {
95ebbc 656                 $server_info = array(
T 657                     'major' => $tmp[1],
658                     'minor' => $tmp[2],
659                     'patch' => $tmp[3],
660                     'extra' => null,
661                     'native' => $server_info,
662                 );
663             } else {
664                 $server_info = array(
665                     'major' => null,
666                     'minor' => null,
667                     'patch' => null,
668                     'extra' => null,
669                     'native' => $server_info,
670                 );
671             }
672         }
673         return $server_info;
674     }
675
676     // }}}
677     // {{{ _checkSequence
678
679     /**
680      * Checks if there's a sequence that exists.
681      *
682      * @param  string $seq_name    The sequence name to verify.
683      * @return bool   $tableExists The value if the table exists or not
684      * @access private
685      */
686     function _checkSequence($seq_name)
687     {
688         $query = "SELECT * FROM $seq_name";
689         $tableExists =& $this->_doQuery($query, true);
690         if (PEAR::isError($tableExists)) {
691             if ($tableExists->getCode() == MDB2_ERROR_NOSUCHTABLE) {
692                 return false;
693             }
694             //return $tableExists;
695             return false;
696         }
697         return mssql_result($tableExists, 0, 0);
698     }
699
700     // }}}
701     // {{{ nextID()
702
703     /**
704      * Returns the next free id of a sequence
705      *
706      * @param string $seq_name name of the sequence
707      * @param boolean $ondemand when true the sequence is
708      *                          automatic created, if it
709      *                          not exists
710      *
711      * @return mixed MDB2 Error Object or id
712      * @access public
713      */
714     function nextID($seq_name, $ondemand = true)
715     {
716         $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
717         $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
d1403f 718         $this->pushErrorHandling(PEAR_ERROR_RETURN);
95ebbc 719         $this->expectError(MDB2_ERROR_NOSUCHTABLE);
T 720         
721         $seq_val = $this->_checkSequence($sequence_name);
722
723         if ($seq_val) {
724             $query = "SET IDENTITY_INSERT $sequence_name OFF ".
725                      "INSERT INTO $sequence_name DEFAULT VALUES";
726         } else {
727             $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (0)";
728         }
729         $result =& $this->_doQuery($query, true);
730         $this->popExpect();
d1403f 731         $this->popErrorHandling();
95ebbc 732         if (PEAR::isError($result)) {
T 733             if ($ondemand && !$this->_checkSequence($sequence_name)) {
734                 $this->loadModule('Manager', null, true);
735                 $result = $this->manager->createSequence($seq_name);
736                 if (PEAR::isError($result)) {
737                     return $this->raiseError($result, null, null,
738                         'on demand sequence '.$seq_name.' could not be created', __FUNCTION__);
739                 } else {
740                     /**
741                      * Little off-by-one problem with the sequence emulation
742                      * here being fixed, that instead of re-calling nextID
743                      * and forcing an increment by one, we simply check if it
744                      * exists, then we get the last inserted id if it does.
745                      *
746                      * In theory, $seq_name should be created otherwise there would
747                      * have been an error thrown somewhere up there.. 
748                      *
749                      * @todo confirm
750                      */
751                     if ($this->_checkSequence($seq_name)) {
752                         return $this->lastInsertID($seq_name);
753                     }
754
755                     return $this->nextID($seq_name, false);
756                 }
757             }
758             return $result;
759         }
760         $value = $this->lastInsertID($sequence_name);
761         if (is_numeric($value)) {
762             $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value";
763             $result =& $this->_doQuery($query, true);
764             if (PEAR::isError($result)) {
765                 $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
766             }
767         }
768         return $value;
769     }
770
771     // }}}
772     // {{{ lastInsertID()
773
774     /**
775      * Returns the autoincrement ID if supported or $id or fetches the current
776      * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field)
777      *
778      * @param string $table name of the table into which a new row was inserted
779      * @param string $field name of the field into which a new row was inserted
d1403f 780      *
95ebbc 781      * @return mixed MDB2 Error Object or id
T 782      * @access public
783      */
784     function lastInsertID($table = null, $field = null)
785     {
786         $server_info = $this->getServerVersion();
787         if (is_array($server_info) && !is_null($server_info['major'])
788            && $server_info['major'] >= 8
789         ) {
d1403f 790             $query = "SELECT IDENT_CURRENT('$table')";
95ebbc 791         } else {
T 792             $query = "SELECT @@IDENTITY";
d1403f 793             if (!is_null($table)) {
A 794                 $query .= ' FROM '.$this->quoteIdentifier($table, true);
795             }
95ebbc 796         }
T 797
798         return $this->queryOne($query, 'integer');
799     }
800
801     // }}}
802 }
803
804 // }}}
805 // {{{ Class MDB2_Result_mssql
806
807 /**
808  * MDB2 MSSQL Server result driver
809  *
810  * @package MDB2
811  * @category Database
812  * @author  Frank M. Kromann <frank@kromann.info>
813  */
814 class MDB2_Result_mssql extends MDB2_Result_Common
815 {
816     // {{{ _skipLimitOffset()
817
818     /**
819      * Skip the first row of a result set.
820      *
821      * @param resource $result
822      * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure
823      * @access protected
824      */
825     function _skipLimitOffset()
826     {
827         if ($this->limit) {
828             if ($this->rownum >= $this->limit) {
829                 return false;
830             }
831         }
832         if ($this->offset) {
833             while ($this->offset_count < $this->offset) {
834                 ++$this->offset_count;
835                 if (!is_array(@mssql_fetch_row($this->result))) {
836                     $this->offset_count = $this->limit;
837                     return false;
838                 }
839             }
840         }
841         return MDB2_OK;
842     }
843
844     // }}}
845     // {{{ fetchRow()
846
847     /**
848      * Fetch a row and insert the data into an existing array.
849      *
850      * @param int       $fetchmode  how the array data should be indexed
851      * @param int    $rownum    number of the row where the data can be found
852      * @return int data array on success, a MDB2 error on failure
853      * @access public
854      */
855     function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
856     {
857         if (!$this->_skipLimitOffset()) {
858             $null = null;
859             return $null;
860         }
861         if (!is_null($rownum)) {
862             $seek = $this->seek($rownum);
863             if (PEAR::isError($seek)) {
864                 return $seek;
865             }
866         }
867         if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
868             $fetchmode = $this->db->fetchmode;
869         }
870         if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
871             $row = @mssql_fetch_assoc($this->result);
872             if (is_array($row)
873                 && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
874             ) {
875                 $row = array_change_key_case($row, $this->db->options['field_case']);
876             }
877         } else {
878             $row = @mssql_fetch_row($this->result);
879         }
880         if (!$row) {
881             if ($this->result === false) {
882                 $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
883                     'resultset has already been freed', __FUNCTION__);
884                 return $err;
885             }
886             $null = null;
887             return $null;
888         }
889         $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
890         $rtrim = false;
891         if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) {
892             if (empty($this->types)) {
893                 $mode += MDB2_PORTABILITY_RTRIM;
894             } else {
895                 $rtrim = true;
896             }
897         }
898         if ($mode) {
899             $this->db->_fixResultArrayValues($row, $mode);
900         }
901         if (!empty($this->types)) {
902             $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim);
903         }
904         if (!empty($this->values)) {
905             $this->_assignBindColumns($row);
906         }
907         if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
908             $object_class = $this->db->options['fetch_class'];
909             if ($object_class == 'stdClass') {
910                 $row = (object) $row;
911             } else {
912                 $row = &new $object_class($row);
913             }
914         }
915         ++$this->rownum;
916         return $row;
917     }
918
919     // }}}
920     // {{{ _getColumnNames()
921
922     /**
923      * Retrieve the names of columns returned by the DBMS in a query result.
924      *
925      * @return  mixed   Array variable that holds the names of columns as keys
926      *                  or an MDB2 error on failure.
927      *                  Some DBMS may not return any columns when the result set
928      *                  does not contain any rows.
929      * @access private
930      */
931     function _getColumnNames()
932     {
933         $columns = array();
934         $numcols = $this->numCols();
935         if (PEAR::isError($numcols)) {
936             return $numcols;
937         }
938         for ($column = 0; $column < $numcols; $column++) {
939             $column_name = @mssql_field_name($this->result, $column);
940             $columns[$column_name] = $column;
941         }
942         if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
943             $columns = array_change_key_case($columns, $this->db->options['field_case']);
944         }
945         return $columns;
946     }
947
948     // }}}
949     // {{{ numCols()
950
951     /**
952      * Count the number of columns returned by the DBMS in a query result.
953      *
954      * @return mixed integer value with the number of columns, a MDB2 error
955      *      on failure
956      * @access public
957      */
958     function numCols()
959     {
960         $cols = @mssql_num_fields($this->result);
961         if (is_null($cols)) {
962             if ($this->result === false) {
963                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
964                     'resultset has already been freed', __FUNCTION__);
965             } elseif (is_null($this->result)) {
966                 return count($this->types);
967             }
968             return $this->db->raiseError(null, null, null,
969                 'Could not get column count', __FUNCTION__);
970         }
971         return $cols;
972     }
973
974     // }}}
975     // {{{ nextResult()
976
977     /**
978      * Move the internal result pointer to the next available result
979      *
980      * @return true on success, false if there is no more result set or an error object on failure
981      * @access public
982      */
983     function nextResult()
984     {
985         if ($this->result === false) {
986             return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
987                 'resultset has already been freed', __FUNCTION__);
988         } elseif (is_null($this->result)) {
989             return false;
990         }
991         return @mssql_next_result($this->result);
992     }
993
994     // }}}
995     // {{{ free()
996
997     /**
998      * Free the internal resources associated with $result.
999      *
1000      * @return boolean true on success, false if $result is invalid
1001      * @access public
1002      */
1003     function free()
1004     {
1005         if (is_resource($this->result) && $this->db->connection) {
1006             $free = @mssql_free_result($this->result);
1007             if ($free === false) {
1008                 return $this->db->raiseError(null, null, null,
1009                     'Could not free result', __FUNCTION__);
1010             }
1011         }
1012         $this->result = false;
1013         return MDB2_OK;
1014     }
1015
1016     // }}}
1017 }
1018
1019 // }}}
1020 // {{{ class MDB2_BufferedResult_mssql
1021
1022 /**
1023  * MDB2 MSSQL Server buffered result driver
1024  *
1025  * @package MDB2
1026  * @category Database
1027  * @author  Frank M. Kromann <frank@kromann.info>
1028  */
1029 class MDB2_BufferedResult_mssql extends MDB2_Result_mssql
1030 {
1031     // {{{ seek()
1032
1033     /**
1034      * Seek to a specific row in a result set
1035      *
1036      * @param int    $rownum    number of the row where the data can be found
1037      * @return mixed MDB2_OK on success, a MDB2 error on failure
1038      * @access public
1039      */
1040     function seek($rownum = 0)
1041     {
1042         if ($this->rownum != ($rownum - 1) && !@mssql_data_seek($this->result, $rownum)) {
1043             if ($this->result === false) {
1044                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1045                     'resultset has already been freed', __FUNCTION__);
1046             } elseif (is_null($this->result)) {
1047                 return MDB2_OK;
1048             }
1049             return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
1050                 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__);
1051         }
1052         $this->rownum = $rownum - 1;
1053         return MDB2_OK;
1054     }
1055
1056     // }}}
1057     // {{{ valid()
1058
1059     /**
1060      * Check if the end of the result set has been reached
1061      *
1062      * @return mixed true or false on sucess, a MDB2 error on failure
1063      * @access public
1064      */
1065     function valid()
1066     {
1067         $numrows = $this->numRows();
1068         if (PEAR::isError($numrows)) {
1069             return $numrows;
1070         }
1071         return $this->rownum < ($numrows - 1);
1072     }
1073
1074     // }}}
1075     // {{{ numRows()
1076
1077     /**
1078      * Returns the number of rows in a result object
1079      *
1080      * @return mixed MDB2 Error Object or the number of rows
1081      * @access public
1082      */
1083     function numRows()
1084     {
1085         $rows = @mssql_num_rows($this->result);
1086         if (is_null($rows)) {
1087             if ($this->result === false) {
1088                 return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1089                     'resultset has already been freed', __FUNCTION__);
1090             } elseif (is_null($this->result)) {
1091                 return 0;
1092             }
1093             return $this->db->raiseError(null, null, null,
1094                 'Could not get row count', __FUNCTION__);
1095         }
1096         if ($this->limit) {
1097             $rows -= $this->limit -1 + $this->offset;
1098             if ($rows < 0) {
1099                 $rows = 0;
1100             }
1101         }
1102         return $rows;
1103     }
1104 }
1105
1106 // }}}
1107 // {{{ MDB2_Statement_mssql
1108
1109 /**
1110  * MDB2 MSSQL Server statement driver
1111  *
1112  * @package MDB2
1113  * @category Database
1114  * @author  Frank M. Kromann <frank@kromann.info>
1115  */
1116 class MDB2_Statement_mssql extends MDB2_Statement_Common
1117 {
1118
1119 }
1120
1121 // }}}
1122 ?>