DB
[ class tree: DB ] [ index: DB ] [ all elements ]

Source for file mssql.php

Documentation is available at mssql.php

  1. <?php
  2.  
  3. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  4.  
  5. /**
  6.  * The PEAR DB driver for PHP's mssql extension
  7.  * for interacting with Microsoft SQL Server databases
  8.  *
  9.  * PHP versions 4 and 5
  10.  *
  11.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  12.  * that is available through the world-wide-web at the following URI:
  13.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  14.  * the PHP License and are unable to obtain it through the web, please
  15.  * send a note to license@php.net so we can mail you a copy immediately.
  16.  *
  17.  * @category   Database
  18.  * @package    DB
  19.  * @author     Sterling Hughes <sterling@php.net>
  20.  * @author     Daniel Convissor <danielc@php.net>
  21.  * @copyright  1997-2005 The PHP Group
  22.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  23.  * @version    CVS: $Id: mssql.php,v 1.90 2007/01/12 05:16:22 aharvey Exp $
  24.  * @link       http://pear.php.net/package/DB
  25.  */
  26.  
  27. /**
  28.  * Obtain the DB_common class so it can be extended from
  29.  */
  30. require_once 'DB/common.php';
  31.  
  32. /**
  33.  * The methods PEAR DB uses to interact with PHP's mssql extension
  34.  * for interacting with Microsoft SQL Server databases
  35.  *
  36.  * These methods overload the ones declared in DB_common.
  37.  *
  38.  * DB's mssql driver is only for Microsfoft SQL Server databases.
  39.  *
  40.  * If you're connecting to a Sybase database, you MUST specify "sybase"
  41.  * as the "phptype" in the DSN.
  42.  *
  43.  * This class only works correctly if you have compiled PHP using
  44.  * --with-mssql=[dir_to_FreeTDS].
  45.  *
  46.  * @category   Database
  47.  * @package    DB
  48.  * @author     Sterling Hughes <sterling@php.net>
  49.  * @author     Daniel Convissor <danielc@php.net>
  50.  * @copyright  1997-2005 The PHP Group
  51.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  52.  * @version    Release: 1.7.10
  53.  * @link       http://pear.php.net/package/DB
  54.  */
  55. class DB_mssql extends DB_common
  56. {
  57.     // {{{ properties
  58.  
  59.     
  60.     /**
  61.      * The DB driver type (mysql, oci8, odbc, etc.)
  62.      * @var string 
  63.      */
  64.     var $phptype = 'mssql';
  65.  
  66.     /**
  67.      * The database syntax variant to be used (db2, access, etc.), if any
  68.      * @var string 
  69.      */
  70.     var $dbsyntax = 'mssql';
  71.  
  72.     /**
  73.      * The capabilities of this DB implementation
  74.      *
  75.      * The 'new_link' element contains the PHP version that first provided
  76.      * new_link support for this DBMS.  Contains false if it's unsupported.
  77.      *
  78.      * Meaning of the 'limit' element:
  79.      *   + 'emulate' = emulate with fetch row by number
  80.      *   + 'alter'   = alter the query
  81.      *   + false     = skip rows
  82.      *
  83.      * @var array 
  84.      */
  85.     var $features = array(
  86.         'limit'         => 'emulate',
  87.         'new_link'      => false,
  88.         'numrows'       => true,
  89.         'pconnect'      => true,
  90.         'prepare'       => false,
  91.         'ssl'           => false,
  92.         'transactions'  => true,
  93.     );
  94.  
  95.     /**
  96.      * A mapping of native error codes to DB error codes
  97.      * @var array 
  98.      */
  99.     // XXX Add here error codes ie: 'S100E' => DB_ERROR_SYNTAX
  100.         var $errorcode_map = array(
  101.         102   => DB_ERROR_SYNTAX,
  102.         110   => DB_ERROR_VALUE_COUNT_ON_ROW,
  103.         155   => DB_ERROR_NOSUCHFIELD,
  104.         156   => DB_ERROR_SYNTAX,
  105.         170   => DB_ERROR_SYNTAX,
  106.         207   => DB_ERROR_NOSUCHFIELD,
  107.         208   => DB_ERROR_NOSUCHTABLE,
  108.         245   => DB_ERROR_INVALID_NUMBER,
  109.         319   => DB_ERROR_SYNTAX,
  110.         321   => DB_ERROR_NOSUCHFIELD,
  111.         325   => DB_ERROR_SYNTAX,
  112.         336   => DB_ERROR_SYNTAX,
  113.         515   => DB_ERROR_CONSTRAINT_NOT_NULL,
  114.         547   => DB_ERROR_CONSTRAINT,
  115.         1018  => DB_ERROR_SYNTAX,
  116.         1035  => DB_ERROR_SYNTAX,
  117.         1913  => DB_ERROR_ALREADY_EXISTS,
  118.         2209  => DB_ERROR_SYNTAX,
  119.         2223  => DB_ERROR_SYNTAX,
  120.         2248  => DB_ERROR_SYNTAX,
  121.         2256  => DB_ERROR_SYNTAX,
  122.         2257  => DB_ERROR_SYNTAX,
  123.         2627  => DB_ERROR_CONSTRAINT,
  124.         2714  => DB_ERROR_ALREADY_EXISTS,
  125.         3607  => DB_ERROR_DIVZERO,
  126.         3701  => DB_ERROR_NOSUCHTABLE,
  127.         7630  => DB_ERROR_SYNTAX,
  128.         8134  => DB_ERROR_DIVZERO,
  129.         9303  => DB_ERROR_SYNTAX,
  130.         9317  => DB_ERROR_SYNTAX,
  131.         9318  => DB_ERROR_SYNTAX,
  132.         9331  => DB_ERROR_SYNTAX,
  133.         9332  => DB_ERROR_SYNTAX,
  134.         15253 => DB_ERROR_SYNTAX,
  135.     );
  136.  
  137.     /**
  138.      * The raw database connection created by PHP
  139.      * @var resource 
  140.      */
  141.     var $connection;
  142.  
  143.     /**
  144.      * The DSN information for connecting to a database
  145.      * @var array 
  146.      */
  147.     var $dsn = array();
  148.  
  149.  
  150.     /**
  151.      * Should data manipulation queries be committed automatically?
  152.      * @var bool 
  153.      * @access private
  154.      */
  155.     var $autocommit = true;
  156.  
  157.     /**
  158.      * The quantity of transactions begun
  159.      *
  160.      * {@internal  While this is private, it can't actually be designated
  161.      * private in PHP 5 because it is directly accessed in the test suite.}}
  162.      *
  163.      * @var integer 
  164.      * @access private
  165.      */
  166.     var $transaction_opcount = 0;
  167.  
  168.     /**
  169.      * The database specified in the DSN
  170.      *
  171.      * It's a fix to allow calls to different databases in the same script.
  172.      *
  173.      * @var string 
  174.      * @access private
  175.      */
  176.     var $_db = null;
  177.  
  178.  
  179.     // }}}
  180.     // {{{ constructor
  181.  
  182.     
  183.     /**
  184.      * This constructor calls <kbd>$this->DB_common()</kbd>
  185.      *
  186.      * @return void 
  187.      */
  188.     function DB_mssql()
  189.     {
  190.         $this->DB_common();
  191.     }
  192.  
  193.     // }}}
  194.     // {{{ connect()
  195.  
  196.     
  197.     /**
  198.      * Connect to the database server, log in and open the database
  199.      *
  200.      * Don't call this method directly.  Use DB::connect() instead.
  201.      *
  202.      * @param array $dsn         the data source name
  203.      * @param bool  $persistent  should the connection be persistent?
  204.      *
  205.      * @return int  DB_OK on success. A DB_Error object on failure.
  206.      */
  207.     function connect($dsn$persistent = false)
  208.     {
  209.         if (!PEAR::loadExtension('mssql'&& !PEAR::loadExtension('sybase')
  210.             && !PEAR::loadExtension('sybase_ct'))
  211.         {
  212.             return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  213.         }
  214.  
  215.         $this->dsn = $dsn;
  216.         if ($dsn['dbsyntax']{
  217.             $this->dbsyntax = $dsn['dbsyntax'];
  218.         }
  219.  
  220.         $params = array(
  221.             $dsn['hostspec'$dsn['hostspec''localhost',
  222.             $dsn['username'$dsn['username': null,
  223.             $dsn['password'$dsn['password': null,
  224.         );
  225.         if ($dsn['port']{
  226.             $params[0.= ((substr(PHP_OS03== 'WIN'',' ':')
  227.                         . $dsn['port'];
  228.         }
  229.  
  230.         $connect_function $persistent 'mssql_pconnect' 'mssql_connect';
  231.  
  232.         $this->connection = @call_user_func_array($connect_function$params);
  233.  
  234.         if (!$this->connection{
  235.             return $this->raiseError(DB_ERROR_CONNECT_FAILED,
  236.                                      nullnullnull,
  237.                                      @mssql_get_last_message());
  238.         }
  239.         if ($dsn['database']{
  240.             if (!@mssql_select_db($dsn['database']$this->connection)) {
  241.                 return $this->raiseError(DB_ERROR_NODBSELECTED,
  242.                                          nullnullnull,
  243.                                          @mssql_get_last_message());
  244.             }
  245.             $this->_db $dsn['database'];
  246.         }
  247.         return DB_OK;
  248.     }
  249.  
  250.     // }}}
  251.     // {{{ disconnect()
  252.  
  253.     
  254.     /**
  255.      * Disconnects from the database server
  256.      *
  257.      * @return bool  TRUE on success, FALSE on failure
  258.      */
  259.     function disconnect()
  260.     {
  261.         $ret @mssql_close($this->connection);
  262.         $this->connection = null;
  263.         return $ret;
  264.     }
  265.  
  266.     // }}}
  267.     // {{{ simpleQuery()
  268.  
  269.     
  270.     /**
  271.      * Sends a query to the database server
  272.      *
  273.      * @param string  the SQL query string
  274.      *
  275.      * @return mixed  + a PHP result resrouce for successful SELECT queries
  276.      *                 + the DB_OK constant for other successful queries
  277.      *                 + a DB_Error object on failure
  278.      */
  279.     function simpleQuery($query)
  280.     {
  281.         $ismanip $this->_checkManip($query);
  282.         $this->last_query = $query;
  283.         if (!@mssql_select_db($this->_db$this->connection)) {
  284.             return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
  285.         }
  286.         $query $this->modifyQuery($query);
  287.         if (!$this->autocommit && $ismanip{
  288.             if ($this->transaction_opcount == 0{
  289.                 $result @mssql_query('BEGIN TRAN'$this->connection);
  290.                 if (!$result{
  291.                     return $this->mssqlRaiseError();
  292.                 }
  293.             }
  294.             $this->transaction_opcount++;
  295.         }
  296.         $result @mssql_query($query$this->connection);
  297.         if (!$result{
  298.             return $this->mssqlRaiseError();
  299.         }
  300.         // Determine which queries that should return data, and which
  301.         // should return an error code only.
  302.         return $ismanip DB_OK : $result;
  303.     }
  304.  
  305.     // }}}
  306.     // {{{ nextResult()
  307.  
  308.     
  309.     /**
  310.      * Move the internal mssql result pointer to the next available result
  311.      *
  312.      * @param valid fbsql result resource
  313.      *
  314.      * @access public
  315.      *
  316.      * @return true if a result is available otherwise return false
  317.      */
  318.     function nextResult($result)
  319.     {
  320.         return @mssql_next_result($result);
  321.     }
  322.  
  323.     // }}}
  324.     // {{{ fetchInto()
  325.  
  326.     
  327.     /**
  328.      * Places a row from the result set into the given array
  329.      *
  330.      * Formating of the array and the data therein are configurable.
  331.      * See DB_result::fetchInto() for more information.
  332.      *
  333.      * This method is not meant to be called directly.  Use
  334.      * DB_result::fetchInto() instead.  It can't be declared "protected"
  335.      * because DB_result is a separate object.
  336.      *
  337.      * @param resource $result    the query result resource
  338.      * @param array    $arr       the referenced array to put the data in
  339.      * @param int      $fetchmode how the resulting array should be indexed
  340.      * @param int      $rownum    the row number to fetch (0 = first row)
  341.      *
  342.      * @return mixed  DB_OK on success, NULL when the end of a result set is
  343.      *                  reached or on failure
  344.      *
  345.      * @see DB_result::fetchInto()
  346.      */
  347.     function fetchInto($result&$arr$fetchmode$rownum = null)
  348.     {
  349.         if ($rownum !== null{
  350.             if (!@mssql_data_seek($result$rownum)) {
  351.                 return null;
  352.             }
  353.         }
  354.         if ($fetchmode DB_FETCHMODE_ASSOC{
  355.             $arr @mssql_fetch_assoc($result);
  356.             if ($this->options['portability'DB_PORTABILITY_LOWERCASE && $arr{
  357.                 $arr array_change_key_case($arrCASE_LOWER);
  358.             }
  359.         else {
  360.             $arr @mssql_fetch_row($result);
  361.         }
  362.         if (!$arr{
  363.             return null;
  364.         }
  365.         if ($this->options['portability'DB_PORTABILITY_RTRIM{
  366.             $this->_rtrimArrayValues($arr);
  367.         }
  368.         if ($this->options['portability'DB_PORTABILITY_NULL_TO_EMPTY{
  369.             $this->_convertNullArrayValuesToEmpty($arr);
  370.         }
  371.         return DB_OK;
  372.     }
  373.  
  374.     // }}}
  375.     // {{{ freeResult()
  376.  
  377.     
  378.     /**
  379.      * Deletes the result set and frees the memory occupied by the result set
  380.      *
  381.      * This method is not meant to be called directly.  Use
  382.      * DB_result::free() instead.  It can't be declared "protected"
  383.      * because DB_result is a separate object.
  384.      *
  385.      * @param resource $result  PHP's query result resource
  386.      *
  387.      * @return bool  TRUE on success, FALSE if $result is invalid
  388.      *
  389.      * @see DB_result::free()
  390.      */
  391.     function freeResult($result)
  392.     {
  393.         return is_resource($result? mssql_free_result($result: false;
  394.     }
  395.  
  396.     // }}}
  397.     // {{{ numCols()
  398.  
  399.     
  400.     /**
  401.      * Gets the number of columns in a result set
  402.      *
  403.      * This method is not meant to be called directly.  Use
  404.      * DB_result::numCols() instead.  It can't be declared "protected"
  405.      * because DB_result is a separate object.
  406.      *
  407.      * @param resource $result  PHP's query result resource
  408.      *
  409.      * @return int  the number of columns.  A DB_Error object on failure.
  410.      *
  411.      * @see DB_result::numCols()
  412.      */
  413.     function numCols($result)
  414.     {
  415.         $cols @mssql_num_fields($result);
  416.         if (!$cols{
  417.             return $this->mssqlRaiseError();
  418.         }
  419.         return $cols;
  420.     }
  421.  
  422.     // }}}
  423.     // {{{ numRows()
  424.  
  425.     
  426.     /**
  427.      * Gets the number of rows in a result set
  428.      *
  429.      * This method is not meant to be called directly.  Use
  430.      * DB_result::numRows() instead.  It can't be declared "protected"
  431.      * because DB_result is a separate object.
  432.      *
  433.      * @param resource $result  PHP's query result resource
  434.      *
  435.      * @return int  the number of rows.  A DB_Error object on failure.
  436.      *
  437.      * @see DB_result::numRows()
  438.      */
  439.     function numRows($result)
  440.     {
  441.         $rows @mssql_num_rows($result);
  442.         if ($rows === false{
  443.             return $this->mssqlRaiseError();
  444.         }
  445.         return $rows;
  446.     }
  447.  
  448.     // }}}
  449.     // {{{ autoCommit()
  450.  
  451.     
  452.     /**
  453.      * Enables or disables automatic commits
  454.      *
  455.      * @param bool $onoff  true turns it on, false turns it off
  456.      *
  457.      * @return int  DB_OK on success.  A DB_Error object if the driver
  458.      *                doesn't support auto-committing transactions.
  459.      */
  460.     function autoCommit($onoff = false)
  461.     {
  462.         // XXX if $this->transaction_opcount > 0, we should probably
  463.         // issue a warning here.
  464.         $this->autocommit $onoff ? true : false;
  465.         return DB_OK;
  466.     }
  467.  
  468.     // }}}
  469.     // {{{ commit()
  470.  
  471.     
  472.     /**
  473.      * Commits the current transaction
  474.      *
  475.      * @return int  DB_OK on success.  A DB_Error object on failure.
  476.      */
  477.     function commit()
  478.     {
  479.         if ($this->transaction_opcount > 0{
  480.             if (!@mssql_select_db($this->_db$this->connection)) {
  481.                 return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
  482.             }
  483.             $result @mssql_query('COMMIT TRAN'$this->connection);
  484.             $this->transaction_opcount = 0;
  485.             if (!$result{
  486.                 return $this->mssqlRaiseError();
  487.             }
  488.         }
  489.         return DB_OK;
  490.     }
  491.  
  492.     // }}}
  493.     // {{{ rollback()
  494.  
  495.     
  496.     /**
  497.      * Reverts the current transaction
  498.      *
  499.      * @return int  DB_OK on success.  A DB_Error object on failure.
  500.      */
  501.     function rollback()
  502.     {
  503.         if ($this->transaction_opcount > 0{
  504.             if (!@mssql_select_db($this->_db$this->connection)) {
  505.                 return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
  506.             }
  507.             $result @mssql_query('ROLLBACK TRAN'$this->connection);
  508.             $this->transaction_opcount = 0;
  509.             if (!$result{
  510.                 return $this->mssqlRaiseError();
  511.             }
  512.         }
  513.         return DB_OK;
  514.     }
  515.  
  516.     // }}}
  517.     // {{{ affectedRows()
  518.  
  519.     
  520.     /**
  521.      * Determines the number of rows affected by a data maniuplation query
  522.      *
  523.      * 0 is returned for queries that don't manipulate data.
  524.      *
  525.      * @return int  the number of rows.  A DB_Error object on failure.
  526.      */
  527.     function affectedRows()
  528.     {
  529.         if ($this->_last_query_manip{
  530.             $res @mssql_query('select @@rowcount'$this->connection);
  531.             if (!$res{
  532.                 return $this->mssqlRaiseError();
  533.             }
  534.             $ar @mssql_fetch_row($res);
  535.             if (!$ar{
  536.                 $result = 0;
  537.             else {
  538.                 @mssql_free_result($res);
  539.                 $result $ar[0];
  540.             }
  541.         else {
  542.             $result = 0;
  543.         }
  544.         return $result;
  545.     }
  546.  
  547.     // }}}
  548.     // {{{ nextId()
  549.  
  550.     
  551.     /**
  552.      * Returns the next free id in a sequence
  553.      *
  554.      * @param string  $seq_name  name of the sequence
  555.      * @param boolean $ondemand  when true, the seqence is automatically
  556.      *                             created if it does not exist
  557.      *
  558.      * @return int  the next id number in the sequence.
  559.      *                A DB_Error object on failure.
  560.      *
  561.      * @see DB_common::nextID(), DB_common::getSequenceName(),
  562.      *       DB_mssql::createSequence(), DB_mssql::dropSequence()
  563.      */
  564.     function nextId($seq_name$ondemand = true)
  565.     {
  566.         $seqname $this->getSequenceName($seq_name);
  567.         if (!@mssql_select_db($this->_db$this->connection)) {
  568.             return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
  569.         }
  570.         $repeat = 0;
  571.         do {
  572.             $this->pushErrorHandling(PEAR_ERROR_RETURN);
  573.             $result $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
  574.             $this->popErrorHandling();
  575.             if ($ondemand && DB::isError($result&&
  576.                 ($result->getCode(== DB_ERROR || $result->getCode(== DB_ERROR_NOSUCHTABLE))
  577.             {
  578.                 $repeat = 1;
  579.                 $result $this->createSequence($seq_name);
  580.                 if (DB::isError($result)) {
  581.                     return $this->raiseError($result);
  582.                 }
  583.             elseif (!DB::isError($result)) {
  584.                 $result =$this->query("SELECT IDENT_CURRENT('$seqname')");
  585.                 if (DB::isError($result)) {
  586.                     /* Fallback code for MS SQL Server 7.0, which doesn't have
  587.                      * IDENT_CURRENT. This is *not* safe for concurrent
  588.                      * requests, and really, if you're using it, you're in a
  589.                      * world of hurt. Nevertheless, it's here to ensure BC. See
  590.                      * bug #181 for the gory details.*/
  591.                     $result =$this->query("SELECT @@IDENTITY FROM $seqname");
  592.                 }
  593.                 $repeat = 0;
  594.             else {
  595.                 $repeat = false;
  596.             }
  597.         while ($repeat);
  598.         if (DB::isError($result)) {
  599.             return $this->raiseError($result);
  600.         }
  601.         $result $result->fetchRow(DB_FETCHMODE_ORDERED);
  602.         return $result[0];
  603.     }
  604.  
  605.     /**
  606.      * Creates a new sequence
  607.      *
  608.      * @param string $seq_name  name of the new sequence
  609.      *
  610.      * @return int  DB_OK on success.  A DB_Error object on failure.
  611.      *
  612.      * @see DB_common::createSequence(), DB_common::getSequenceName(),
  613.      *       DB_mssql::nextID(), DB_mssql::dropSequence()
  614.      */
  615.     function createSequence($seq_name)
  616.     {
  617.         return $this->query('CREATE TABLE '
  618.                             . $this->getSequenceName($seq_name)
  619.                             . ' ([id] [int] IDENTITY (1, 1) NOT NULL,'
  620.                             . ' [vapor] [int] NULL)');
  621.     }
  622.  
  623.     // }}}
  624.     // {{{ dropSequence()
  625.  
  626.     
  627.     /**
  628.      * Deletes a sequence
  629.      *
  630.      * @param string $seq_name  name of the sequence to be deleted
  631.      *
  632.      * @return int  DB_OK on success.  A DB_Error object on failure.
  633.      *
  634.      * @see DB_common::dropSequence(), DB_common::getSequenceName(),
  635.      *       DB_mssql::nextID(), DB_mssql::createSequence()
  636.      */
  637.     function dropSequence($seq_name)
  638.     {
  639.         return $this->query('DROP TABLE ' $this->getSequenceName($seq_name));
  640.     }
  641.  
  642.     // }}}
  643.     // {{{ quoteIdentifier()
  644.  
  645.     
  646.     /**
  647.      * Quotes a string so it can be safely used as a table or column name
  648.      *
  649.      * @param string $str  identifier name to be quoted
  650.      *
  651.      * @return string  quoted identifier string
  652.      *
  653.      * @see DB_common::quoteIdentifier()
  654.      * @since Method available since Release 1.6.0
  655.      */
  656.     function quoteIdentifier($str)
  657.     {
  658.         return '[' str_replace(']'']]'$str']';
  659.     }
  660.  
  661.     // }}}
  662.     // {{{ mssqlRaiseError()
  663.  
  664.     
  665.     /**
  666.      * Produces a DB_Error object regarding the current problem
  667.      *
  668.      * @param int $errno  if the error is being manually raised pass a
  669.      *                      DB_ERROR* constant here.  If this isn't passed
  670.      *                      the error information gathered from the DBMS.
  671.      *
  672.      * @return object  the DB_Error object
  673.      *
  674.      * @see DB_common::raiseError(),
  675.      *       DB_mssql::errorNative(), DB_mssql::errorCode()
  676.      */
  677.     function mssqlRaiseError($code = null)
  678.