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