PageRenderTime 55ms CodeModel.GetById 23ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/common/libraries/plugin/pear/MDB2/Driver/mssql.php

https://bitbucket.org/chamilo/chamilo/
PHP | 1133 lines | 898 code | 35 blank | 200 comment | 66 complexity | 8277e94eb986fc424a6d540d1d2ba966 MD5 | raw file
   1<?php
   2// vim: set et ts=4 sw=4 fdm=marker:
   3// +----------------------------------------------------------------------+
   4// | PHP versions 4 and 5                                                 |
   5// +----------------------------------------------------------------------+
   6// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
   7// | Stig. S. Bakken, Lukas Smith, Frank M. Kromann                       |
   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//
  46// $Id: mssql.php 137 2009-11-09 13:24:37Z vanpouckesven $
  47//
  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 */
  56class 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;
  89        $this->supported['triggers'] = true;
  90        $this->supported['auto_increment'] = true;
  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
  97        $this->options['DBA_username'] = false;
  98        $this->options['DBA_password'] = false;
  99        $this->options['database_device'] = false;
 100        $this->options['database_size'] = false;
 101        $this->options['max_identifiers_length'] = 128; // MS Access: 64
 102    }
 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     */
 114    function errorInfo($error = null, $connection = null)
 115    {
 116        if (is_null($connection)) {
 117            $connection = $this->connection;
 118        }
 119
 120        $native_code = null;
 121        if ($connection) {
 122            $result = @mssql_query('select @@ERROR as ErrorCode', $connection);
 123            if ($result) {
 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,
 147                    911   => MDB2_ERROR_NOT_FOUND,
 148                    1018  => MDB2_ERROR_SYNTAX,
 149                    1035  => MDB2_ERROR_SYNTAX,
 150                    1801  => MDB2_ERROR_ALREADY_EXISTS,
 151                    1913  => MDB2_ERROR_ALREADY_EXISTS,
 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    // }}}
 309    // {{{ _doConnect()
 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 ($this->_isNewLinkSet()) {
 334                $params[] = true;
 335            } else {
 336                $params[] = false;
 337            }
 338        }
 339
 340        $connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect';
 341
 342        $connection = @call_user_func_array($connect_function, $params);
 343        if ($connection <= 0) {
 344            return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
 345                'unable to establish a connection', __FUNCTION__, __FUNCTION__);
 346        }
 347
 348        @mssql_query('SET ANSI_NULL_DFLT_ON ON', $connection);
 349
 350        /*
 351        if (!empty($this->dsn['charset'])) {
 352            $result = $this->setCharset($this->dsn['charset'], $connection);
 353            if (PEAR::isError($result)) {
 354                return $result;
 355            }
 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    // }}}
 371    // {{{ connect()
 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
 390        $connection = $this->_doConnect(
 391            $this->dsn['username'],
 392            $this->dsn['password'],
 393            $this->options['persistent']
 394        );
 395        if (PEAR::isError($connection)) {
 396            return $connection;
 397        }
 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;
 417    }
 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;
 452    }
 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                $ok = @mssql_close($this->connection);
 484                if (!$ok) {
 485                    return $this->raiseError(MDB2_ERROR_DISCONNECT_FAILED,
 486                           null, null, null, __FUNCTION__);
 487                }
 488            }
 489        } else {
 490            return false;
 491        }
 492        return parent::disconnect($force);
 493    }
 494
 495    // }}}
 496    // {{{ standaloneQuery()
 497
 498   /**
 499     * execute a query as DBA
 500     *
 501     * @param string $query the SQL query
 502     * @param mixed   $types  array that contains the types of the columns in
 503     *                        the result set
 504     * @param boolean $is_manip  if the query is a manipulation query
 505     * @return mixed MDB2_OK on success, a MDB2 error on failure
 506     * @access public
 507     */
 508    function &standaloneQuery($query, $types = null, $is_manip = false)
 509    {
 510        $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username'];
 511        $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password'];
 512        $connection = $this->_doConnect($user, $pass, $this->options['persistent']);
 513        if (PEAR::isError($connection)) {
 514            return $connection;
 515        }
 516
 517        $offset = $this->offset;
 518        $limit = $this->limit;
 519        $this->offset = $this->limit = 0;
 520        $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
 521        
 522        $result =& $this->_doQuery($query, $is_manip, $connection, $this->database_name);
 523        if (!PEAR::isError($result)) {
 524            $result = $this->_affectedRows($connection, $result);
 525        }
 526
 527        @mssql_close($connection);
 528        return $result;
 529    }
 530
 531    // }}}
 532    // {{{ _doQuery()
 533
 534    /**
 535     * Execute a query
 536     * @param string $query  query
 537     * @param boolean $is_manip  if the query is a manipulation query
 538     * @param resource $connection
 539     * @param string $database_name
 540     * @return result or error object
 541     * @access protected
 542     */
 543    function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null)
 544    {
 545        $this->last_query = $query;
 546        $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
 547        if ($result) {
 548            if (PEAR::isError($result)) {
 549                return $result;
 550            }
 551            $query = $result;
 552        }
 553        if ($this->options['disable_query']) {
 554            $result = $is_manip ? 0 : null;
 555            return $result;
 556        }
 557
 558        if (is_null($connection)) {
 559            $connection = $this->getConnection();
 560            if (PEAR::isError($connection)) {
 561                return $connection;
 562            }
 563        }
 564        if (is_null($database_name)) {
 565            $database_name = $this->database_name;
 566        }
 567
 568        if ($database_name) {
 569            if ($database_name != $this->connected_database_name) {
 570                if (!@mssql_select_db($database_name, $connection)) {
 571                    $err = $this->raiseError(null, null, null,
 572                        'Could not select the database: '.$database_name, __FUNCTION__);
 573                    return $err;
 574                }
 575                $this->connected_database_name = $database_name;
 576            }
 577        }
 578
 579        $result = @mssql_query($query, $connection);
 580        if (!$result) {
 581            $err =& $this->raiseError(null, null, null,
 582                'Could not execute statement', __FUNCTION__);
 583            return $err;
 584        }
 585
 586        $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
 587        return $result;
 588    }
 589
 590    // }}}
 591    // {{{ _affectedRows()
 592
 593    /**
 594     * Returns the number of rows affected
 595     *
 596     * @param resource $result
 597     * @param resource $connection
 598     * @return mixed MDB2 Error Object or the number of rows affected
 599     * @access private
 600     */
 601    function _affectedRows($connection, $result = null)
 602    {
 603        if (is_null($connection)) {
 604            $connection = $this->getConnection();
 605            if (PEAR::isError($connection)) {
 606                return $connection;
 607            }
 608        }
 609        return @mssql_rows_affected($connection);
 610    }
 611
 612    // }}}
 613    // {{{ _modifyQuery()
 614
 615    /**
 616     * Changes a query string for various DBMS specific reasons
 617     *
 618     * @param string $query  query to modify
 619     * @param boolean $is_manip  if it is a DML query
 620     * @param integer $limit  limit the number of rows
 621     * @param integer $offset  start reading from given offset
 622     * @return string modified query
 623     * @access protected
 624     */
 625    function _modifyQuery($query, $is_manip, $limit, $offset)
 626    {
 627        if ($limit > 0) {
 628            $fetch = $offset + $limit;
 629            if (!$is_manip) {
 630                return preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',
 631                    "\\1SELECT\\2 TOP $fetch", $query);
 632            }
 633        }
 634        return $query;
 635    }
 636
 637    // }}}
 638    // {{{ getServerVersion()
 639
 640    /**
 641     * return version information about the server
 642     *
 643     * @param bool   $native  determines if the raw version string should be returned
 644     * @return mixed array/string with version information or MDB2 error object
 645     * @access public
 646     */
 647    function getServerVersion($native = false)
 648    {
 649        if ($this->connected_server_info) {
 650            $server_info = $this->connected_server_info;
 651        } else {
 652            $query = 'SELECT @@VERSION';
 653            $server_info = $this->queryOne($query, 'text');
 654            if (PEAR::isError($server_info)) {
 655                return $server_info;
 656            }
 657        }
 658        // cache server_info
 659        $this->connected_server_info = $server_info;
 660        if (!$native && !PEAR::isError($server_info)) {
 661            if (preg_match('/(\d+)\.(\d+)\.(\d+)/', $server_info, $tmp)) {
 662                $server_info = array(
 663                    'major' => $tmp[1],
 664                    'minor' => $tmp[2],
 665                    'patch' => $tmp[3],
 666                    'extra' => null,
 667                    'native' => $server_info,
 668                );
 669            } else {
 670                $server_info = array(
 671                    'major' => null,
 672                    'minor' => null,
 673                    'patch' => null,
 674                    'extra' => null,
 675                    'native' => $server_info,
 676                );
 677            }
 678        }
 679        return $server_info;
 680    }
 681
 682    // }}}
 683    // {{{ _checkSequence
 684
 685    /**
 686     * Checks if there's a sequence that exists.
 687     *
 688     * @param  string $seq_name    The sequence name to verify.
 689     * @return bool   $tableExists The value if the table exists or not
 690     * @access private
 691     */
 692    function _checkSequence($seq_name)
 693    {
 694        $query = "SELECT * FROM $seq_name";
 695        $tableExists =& $this->_doQuery($query, true);
 696        if (PEAR::isError($tableExists)) {
 697            if ($tableExists->getCode() == MDB2_ERROR_NOSUCHTABLE) {
 698                return false;
 699            }
 700            //return $tableExists;
 701            return false;
 702        }
 703        return mssql_result($tableExists, 0, 0);
 704    }
 705
 706    // }}}
 707    // {{{ nextID()
 708
 709    /**
 710     * Returns the next free id of a sequence
 711     *
 712     * @param string $seq_name name of the sequence
 713     * @param boolean $ondemand when true the sequence is
 714     *                          automatic created, if it
 715     *                          not exists
 716     *
 717     * @return mixed MDB2 Error Object or id
 718     * @access public
 719     */
 720    function nextID($seq_name, $ondemand = true)
 721    {
 722        $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
 723        $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
 724        $this->pushErrorHandling(PEAR_ERROR_RETURN);
 725        $this->expectError(MDB2_ERROR_NOSUCHTABLE);
 726        
 727        $seq_val = $this->_checkSequence($sequence_name);
 728
 729        if ($seq_val) {
 730            $query = "SET IDENTITY_INSERT $sequence_name OFF ".
 731                     "INSERT INTO $sequence_name DEFAULT VALUES";
 732        } else {
 733            $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (0)";
 734        }
 735        $result =& $this->_doQuery($query, true);
 736        $this->popExpect();
 737        $this->popErrorHandling();
 738        if (PEAR::isError($result)) {
 739            if ($ondemand && !$this->_checkSequence($sequence_name)) {
 740                $this->loadModule('Manager', null, true);
 741                $result = $this->manager->createSequence($seq_name);
 742                if (PEAR::isError($result)) {
 743                    return $this->raiseError($result, null, null,
 744                        'on demand sequence '.$seq_name.' could not be created', __FUNCTION__);
 745                } else {
 746                    /**
 747                     * Little off-by-one problem with the sequence emulation
 748                     * here being fixed, that instead of re-calling nextID
 749                     * and forcing an increment by one, we simply check if it
 750                     * exists, then we get the last inserted id if it does.
 751                     *
 752                     * In theory, $seq_name should be created otherwise there would
 753                     * have been an error thrown somewhere up there.. 
 754                     *
 755                     * @todo confirm
 756                     */
 757                    if ($this->_checkSequence($seq_name)) {
 758                        return $this->lastInsertID($seq_name);
 759                    }
 760
 761                    return $this->nextID($seq_name, false);
 762                }
 763            }
 764            return $result;
 765        }
 766        $value = $this->lastInsertID($sequence_name);
 767        if (is_numeric($value)) {
 768            $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value";
 769            $result =& $this->_doQuery($query, true);
 770            if (PEAR::isError($result)) {
 771                $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
 772            }
 773        }
 774        return $value;
 775    }
 776
 777    // }}}
 778    // {{{ lastInsertID()
 779
 780    /**
 781     * Returns the autoincrement ID if supported or $id or fetches the current
 782     * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field)
 783     *
 784     * @param string $table name of the table into which a new row was inserted
 785     * @param string $field name of the field into which a new row was inserted
 786     *
 787     * @return mixed MDB2 Error Object or id
 788     * @access public
 789     */
 790    function lastInsertID($table = null, $field = null)
 791    {
 792        $server_info = $this->getServerVersion();
 793        if (is_array($server_info) && !is_null($server_info['major'])
 794           && $server_info['major'] >= 8
 795        ) {
 796            $query = "SELECT IDENT_CURRENT('$table')";
 797        } else {
 798            $query = "SELECT @@IDENTITY";
 799            if (!is_null($table)) {
 800                $query .= ' FROM '.$this->quoteIdentifier($table, true);
 801            }
 802        }
 803
 804        return $this->queryOne($query, 'integer');
 805    }
 806
 807    // }}}
 808}
 809
 810// }}}
 811// {{{ Class MDB2_Result_mssql
 812
 813/**
 814 * MDB2 MSSQL Server result driver
 815 *
 816 * @package MDB2
 817 * @category Database
 818 * @author  Frank M. Kromann <frank@kromann.info>
 819 */
 820class MDB2_Result_mssql extends MDB2_Result_Common
 821{
 822    // {{{ _skipLimitOffset()
 823
 824    /**
 825     * Skip the first row of a result set.
 826     *
 827     * @param resource $result
 828     * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure
 829     * @access protected
 830     */
 831    function _skipLimitOffset()
 832    {
 833        if ($this->limit) {
 834            if ($this->rownum >= $this->limit) {
 835                return false;
 836            }
 837        }
 838        if ($this->offset) {
 839            while ($this->offset_count < $this->offset) {
 840                ++$this->offset_count;
 841                if (!is_array(@mssql_fetch_row($this->result))) {
 842                    $this->offset_count = $this->limit;
 843                    return false;
 844                }
 845            }
 846        }
 847        return MDB2_OK;
 848    }
 849
 850    // }}}
 851    // {{{ fetchRow()
 852
 853    /**
 854     * Fetch a row and insert the data into an existing array.
 855     *
 856     * @param int       $fetchmode  how the array data should be indexed
 857     * @param int    $rownum    number of the row where the data can be found
 858     * @return int data array on success, a MDB2 error on failure
 859     * @access public
 860     */
 861    function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
 862    {
 863        if (!$this->_skipLimitOffset()) {
 864            $null = null;
 865            return $null;
 866        }
 867        if (!is_null($rownum)) {
 868            $seek = $this->seek($rownum);
 869            if (PEAR::isError($seek)) {
 870                return $seek;
 871            }
 872        }
 873        if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
 874            $fetchmode = $this->db->fetchmode;
 875        }
 876        if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
 877            $row = @mssql_fetch_assoc($this->result);
 878            if (is_array($row)
 879                && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
 880            ) {
 881                $row = array_change_key_case($row, $this->db->options['field_case']);
 882            }
 883        } else {
 884            $row = @mssql_fetch_row($this->result);
 885        }
 886        if (!$row) {
 887            if ($this->result === false) {
 888                $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
 889                    'resultset has already been freed', __FUNCTION__);
 890                return $err;
 891            }
 892            $null = null;
 893            return $null;
 894        }
 895        $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
 896        $rtrim = false;
 897        if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) {
 898            if (empty($this->types)) {
 899                $mode += MDB2_PORTABILITY_RTRIM;
 900            } else {
 901                $rtrim = true;
 902            }
 903        }
 904        if ($mode) {
 905            $this->db->_fixResultArrayValues($row, $mode);
 906        }
 907        if (!($fetchmode & MDB2_FETCHMODE_ASSOC) && !empty($this->types)) {
 908            $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim);
 909        } elseif (($fetchmode & MDB2_FETCHMODE_ASSOC) && !empty($this->types_assoc)) {
 910            $row = $this->db->datatype->convertResultRow($this->types_assoc, $row, $rtrim);
 911        }
 912        if (!empty($this->values)) {
 913            $this->_assignBindColumns($row);
 914        }
 915        if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
 916            $object_class = $this->db->options['fetch_class'];
 917            if ($object_class == 'stdClass') {
 918                $row = (object) $row;
 919            } else {
 920                $row = &new $object_class($row);
 921            }
 922        }
 923        ++$this->rownum;
 924        return $row;
 925    }
 926
 927    // }}}
 928    // {{{ _getColumnNames()
 929
 930    /**
 931     * Retrieve the names of columns returned by the DBMS in a query result.
 932     *
 933     * @return  mixed   Array variable that holds the names of columns as keys
 934     *                  or an MDB2 error on failure.
 935     *                  Some DBMS may not return any columns when the result set
 936     *                  does not contain any rows.
 937     * @access private
 938     */
 939    function _getColumnNames()
 940    {
 941        $columns = array();
 942        $numcols = $this->numCols();
 943        if (PEAR::isError($numcols)) {
 944            return $numcols;
 945        }
 946        for ($column = 0; $column < $numcols; $column++) {
 947            $column_name = @mssql_field_name($this->result, $column);
 948            $columns[$column_name] = $column;
 949        }
 950        if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
 951            $columns = array_change_key_case($columns, $this->db->options['field_case']);
 952        }
 953        return $columns;
 954    }
 955
 956    // }}}
 957    // {{{ numCols()
 958
 959    /**
 960     * Count the number of columns returned by the DBMS in a query result.
 961     *
 962     * @return mixed integer value with the number of columns, a MDB2 error
 963     *      on failure
 964     * @access public
 965     */
 966    function numCols()
 967    {
 968        $cols = @mssql_num_fields($this->result);
 969        if (is_null($cols)) {
 970            if ($this->result === false) {
 971                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
 972                    'resultset has already been freed', __FUNCTION__);
 973            } elseif (is_null($this->result)) {
 974                return count($this->types);
 975            }
 976            return $this->db->raiseError(null, null, null,
 977                'Could not get column count', __FUNCTION__);
 978        }
 979        return $cols;
 980    }
 981
 982    // }}}
 983    // {{{ nextResult()
 984
 985    /**
 986     * Move the internal result pointer to the next available result
 987     *
 988     * @return true on success, false if there is no more result set or an error object on failure
 989     * @access public
 990     */
 991    function nextResult()
 992    {
 993        if ($this->result === false) {
 994            return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
 995                'resultset has already been freed', __FUNCTION__);
 996        } elseif (is_null($this->result)) {
 997            return false;
 998        }
 999        return @mssql_next_result($this->result);
1000    }
1001
1002    // }}}
1003    // {{{ free()
1004
1005    /**
1006     * Free the internal resources associated with $result.
1007     *
1008     * @return boolean true on success, false if $result is invalid
1009     * @access public
1010     */
1011    function free()
1012    {
1013        if (is_resource($this->result) && $this->db->connection) {
1014            $free = @mssql_free_result($this->result);
1015            if ($free === false) {
1016                return $this->db->raiseError(null, null, null,
1017                    'Could not free result', __FUNCTION__);
1018            }
1019        }
1020        $this->result = false;
1021        return MDB2_OK;
1022    }
1023
1024    // }}}
1025}
1026
1027// }}}
1028// {{{ class MDB2_BufferedResult_mssql
1029
1030/**
1031 * MDB2 MSSQL Server buffered result driver
1032 *
1033 * @package MDB2
1034 * @category Database
1035 * @author  Frank M. Kromann <frank@kromann.info>
1036 */
1037class MDB2_BufferedResult_mssql extends MDB2_Result_mssql
1038{
1039    // {{{ seek()
1040
1041    /**
1042     * Seek to a specific row in a result set
1043     *
1044     * @param int    $rownum    number of the row where the data can be found
1045     * @return mixed MDB2_OK on success, a MDB2 error on failure
1046     * @access public
1047     */
1048    function seek($rownum = 0)
1049    {
1050        if ($this->rownum != ($rownum - 1) && !@mssql_data_seek($this->result, $rownum)) {
1051            if ($this->result === false) {
1052                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1053                    'resultset has already been freed', __FUNCTION__);
1054            } elseif (is_null($this->result)) {
1055                return MDB2_OK;
1056            }
1057            return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
1058                'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__);
1059        }
1060        $this->rownum = $rownum - 1;
1061        return MDB2_OK;
1062    }
1063
1064    // }}}
1065    // {{{ valid()
1066
1067    /**
1068     * Check if the end of the result set has been reached
1069     *
1070     * @return mixed true or false on sucess, a MDB2 error on failure
1071     * @access public
1072     */
1073    function valid()
1074    {
1075        $numrows = $this->numRows();
1076        if (PEAR::isError($numrows)) {
1077            return $numrows;
1078        }
1079        return $this->rownum < ($numrows - 1);
1080    }
1081
1082    // }}}
1083    // {{{ numRows()
1084
1085    /**
1086     * Returns the number of rows in a result object
1087     *
1088     * @return mixed MDB2 Error Object or the number of rows
1089     * @access public
1090     */
1091    function numRows()
1092    {
1093        $rows = @mssql_num_rows($this->result);
1094        if (is_null($rows)) {
1095            if ($this->result === false) {
1096                return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
1097                    'resultset has already been freed', __FUNCTION__);
1098            } elseif (is_null($this->result)) {
1099                return 0;
1100            }
1101            return $this->db->raiseError(null, null, null,
1102                'Could not get row count', __FUNCTION__);
1103        }
1104        if ($this->limit) {
1105            $rows -= $this->offset;
1106            if ($rows > $this->limit) {
1107                $rows = $this->limit;
1108            }
1109            if ($rows < 0) {
1110                $rows = 0;
1111            }
1112        }
1113        return $rows;
1114    }
1115}
1116
1117// }}}
1118// {{{ MDB2_Statement_mssql
1119
1120/**
1121 * MDB2 MSSQL Server statement driver
1122 *
1123 * @package MDB2
1124 * @category Database
1125 * @author  Frank M. Kromann <frank@kromann.info>
1126 */
1127class MDB2_Statement_mssql extends MDB2_Statement_Common
1128{
1129
1130}
1131
1132// }}}
1133?>