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

Source for file odbc.php

Documentation is available at odbc.php

  1. <?php
  2.  
  3. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  4.  
  5. /**
  6.  * The PEAR DB driver for PHP's odbc extension
  7.  * for interacting with databases via ODBC connections
  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     Stig Bakken <ssb@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: odbc.php,v 1.80 2007/01/12 03:11:17 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 odbc extension
  34.  * for interacting with databases via ODBC connections
  35.  *
  36.  * These methods overload the ones declared in DB_common.
  37.  *
  38.  * More info on ODBC errors could be found here:
  39.  * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/trblsql/tr_err_odbc_5stz.asp
  40.  *
  41.  * @category   Database
  42.  * @package    DB
  43.  * @author     Stig Bakken <ssb@php.net>
  44.  * @author     Daniel Convissor <danielc@php.net>
  45.  * @copyright  1997-2005 The PHP Group
  46.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  47.  * @version    Release: 1.7.10
  48.  * @link       http://pear.php.net/package/DB
  49.  */
  50. class DB_odbc extends DB_common
  51. {
  52.     // {{{ properties
  53.  
  54.     
  55.     /**
  56.      * The DB driver type (mysql, oci8, odbc, etc.)
  57.      * @var string 
  58.      */
  59.     var $phptype = 'odbc';
  60.  
  61.     /**
  62.      * The database syntax variant to be used (db2, access, etc.), if any
  63.      * @var string 
  64.      */
  65.     var $dbsyntax = 'sql92';
  66.  
  67.     /**
  68.      * The capabilities of this DB implementation
  69.      *
  70.      * The 'new_link' element contains the PHP version that first provided
  71.      * new_link support for this DBMS.  Contains false if it's unsupported.
  72.      *
  73.      * Meaning of the 'limit' element:
  74.      *   + 'emulate' = emulate with fetch row by number
  75.      *   + 'alter'   = alter the query
  76.      *   + false     = skip rows
  77.      *
  78.      * NOTE: The feature set of the following drivers are different than
  79.      * the default:
  80.      *   + solid: 'transactions' = true
  81.      *   + navision: 'limit' = false
  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'  => false,
  93.     );
  94.  
  95.     /**
  96.      * A mapping of native error codes to DB error codes
  97.      * @var array 
  98.      */
  99.     var $errorcode_map = array(
  100.         '01004' => DB_ERROR_TRUNCATED,
  101.         '07001' => DB_ERROR_MISMATCH,
  102.         '21S01' => DB_ERROR_VALUE_COUNT_ON_ROW,
  103.         '21S02' => DB_ERROR_MISMATCH,
  104.         '22001' => DB_ERROR_INVALID,
  105.         '22003' => DB_ERROR_INVALID_NUMBER,
  106.         '22005' => DB_ERROR_INVALID_NUMBER,
  107.         '22008' => DB_ERROR_INVALID_DATE,
  108.         '22012' => DB_ERROR_DIVZERO,
  109.         '23000' => DB_ERROR_CONSTRAINT,
  110.         '23502' => DB_ERROR_CONSTRAINT_NOT_NULL,
  111.         '23503' => DB_ERROR_CONSTRAINT,
  112.         '23504' => DB_ERROR_CONSTRAINT,
  113.         '23505' => DB_ERROR_CONSTRAINT,
  114.         '24000' => DB_ERROR_INVALID,
  115.         '34000' => DB_ERROR_INVALID,
  116.         '37000' => DB_ERROR_SYNTAX,
  117.         '42000' => DB_ERROR_SYNTAX,
  118.         '42601' => DB_ERROR_SYNTAX,
  119.         'IM001' => DB_ERROR_UNSUPPORTED,
  120.         'S0000' => DB_ERROR_NOSUCHTABLE,
  121.         'S0001' => DB_ERROR_ALREADY_EXISTS,
  122.         'S0002' => DB_ERROR_NOSUCHTABLE,
  123.         'S0011' => DB_ERROR_ALREADY_EXISTS,
  124.         'S0012' => DB_ERROR_NOT_FOUND,
  125.         'S0021' => DB_ERROR_ALREADY_EXISTS,
  126.         'S0022' => DB_ERROR_NOSUCHFIELD,
  127.         'S1009' => DB_ERROR_INVALID,
  128.         'S1090' => DB_ERROR_INVALID,
  129.         'S1C00' => DB_ERROR_NOT_CAPABLE,
  130.     );
  131.  
  132.     /**
  133.      * The raw database connection created by PHP
  134.      * @var resource 
  135.      */
  136.     var $connection;
  137.  
  138.     /**
  139.      * The DSN information for connecting to a database
  140.      * @var array 
  141.      */
  142.     var $dsn = array();
  143.  
  144.  
  145.     /**
  146.      * The number of rows affected by a data manipulation query
  147.      * @var integer 
  148.      * @access private
  149.      */
  150.     var $affected = 0;
  151.  
  152.  
  153.     // }}}
  154.     // {{{ constructor
  155.  
  156.     
  157.     /**
  158.      * This constructor calls <kbd>$this->DB_common()</kbd>
  159.      *
  160.      * @return void 
  161.      */
  162.     function DB_odbc()
  163.     {
  164.         $this->DB_common();
  165.     }
  166.  
  167.     // }}}
  168.     // {{{ connect()
  169.  
  170.     
  171.     /**
  172.      * Connect to the database server, log in and open the database
  173.      *
  174.      * Don't call this method directly.  Use DB::connect() instead.
  175.      *
  176.      * PEAR DB's odbc driver supports the following extra DSN options:
  177.      *   + cursor  The type of cursor to be used for this connection.
  178.      *
  179.      * @param array $dsn         the data source name
  180.      * @param bool  $persistent  should the connection be persistent?
  181.      *
  182.      * @return int  DB_OK on success. A DB_Error object on failure.
  183.      */
  184.     function connect($dsn$persistent = false)
  185.     {
  186.         if (!PEAR::loadExtension('odbc')) {
  187.             return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  188.         }
  189.  
  190.         $this->dsn = $dsn;
  191.         if ($dsn['dbsyntax']{
  192.             $this->dbsyntax = $dsn['dbsyntax'];
  193.         }
  194.         switch ($this->dbsyntax{
  195.             case 'access':
  196.             case 'db2':
  197.             case 'solid':
  198.                 $this->features['transactions'= true;
  199.                 break;
  200.             case 'navision':
  201.                 $this->features['limit'= false;
  202.         }
  203.  
  204.         /*
  205.          * This is hear for backwards compatibility. Should have been using
  206.          * 'database' all along, but prior to 1.6.0RC3 'hostspec' was used.
  207.          */
  208.         if ($dsn['database']{
  209.             $odbcdsn $dsn['database'];
  210.         elseif ($dsn['hostspec']{
  211.             $odbcdsn $dsn['hostspec'];
  212.         else {
  213.             $odbcdsn 'localhost';
  214.         }
  215.  
  216.         $connect_function $persistent 'odbc_pconnect' 'odbc_connect';
  217.  
  218.         if (empty($dsn['cursor'])) {
  219.             $this->connection = @$connect_function($odbcdsn$dsn['username'],
  220.                                                    $dsn['password']);
  221.         else {
  222.             $this->connection = @$connect_function($odbcdsn$dsn['username'],
  223.                                                    $dsn['password'],
  224.                                                    $dsn['cursor']);
  225.         }
  226.  
  227.         if (!is_resource($this->connection)) {
  228.             return $this->raiseError(DB_ERROR_CONNECT_FAILED,
  229.                                      nullnullnull,
  230.                                      $this->errorNative());
  231.         }
  232.         return DB_OK;
  233.     }
  234.  
  235.     // }}}
  236.     // {{{ disconnect()
  237.  
  238.     
  239.     /**
  240.      * Disconnects from the database server
  241.      *
  242.      * @return bool  TRUE on success, FALSE on failure
  243.      */
  244.     function disconnect()
  245.     {
  246.         $err @odbc_close($this->connection);
  247.         $this->connection = null;
  248.         return $err;
  249.     }
  250.  
  251.     // }}}
  252.     // {{{ simpleQuery()
  253.  
  254.     
  255.     /**
  256.      * Sends a query to the database server
  257.      *
  258.      * @param string  the SQL query string
  259.      *
  260.      * @return mixed  + a PHP result resrouce for successful SELECT queries
  261.      *                 + the DB_OK constant for other successful queries
  262.      *                 + a DB_Error object on failure
  263.      */
  264.     function simpleQuery($query)
  265.     {
  266.         $this->last_query = $query;
  267.         $query $this->modifyQuery($query);
  268.         $result @odbc_exec($this->connection$query);
  269.         if (!$result{
  270.             return $this->odbcRaiseError()// XXX ERRORMSG
  271.         }
  272.         // Determine which queries that should return data, and which
  273.         // should return an error code only.
  274.         if ($this->_checkManip($query)) {
  275.             $this->affected $result// For affectedRows()
  276.             return DB_OK;
  277.         }
  278.         $this->affected = 0;
  279.         return $result;
  280.     }
  281.  
  282.     // }}}
  283.     // {{{ nextResult()
  284.  
  285.     
  286.     /**
  287.      * Move the internal odbc result pointer to the next available result
  288.      *
  289.      * @param valid fbsql result resource
  290.      *
  291.      * @access public
  292.      *
  293.      * @return true if a result is available otherwise return false
  294.      */
  295.     function nextResult($result)
  296.     {
  297.         return @odbc_next_result($result);
  298.     }
  299.  
  300.     // }}}
  301.     // {{{ fetchInto()
  302.  
  303.     
  304.     /**
  305.      * Places a row from the result set into the given array
  306.      *
  307.      * Formating of the array and the data therein are configurable.
  308.      * See DB_result::fetchInto() for more information.
  309.      *
  310.      * This method is not meant to be called directly.  Use
  311.      * DB_result::fetchInto() instead.  It can't be declared "protected"
  312.      * because DB_result is a separate object.
  313.      *
  314.      * @param resource $result    the query result resource
  315.      * @param array    $arr       the referenced array to put the data in
  316.      * @param int      $fetchmode how the resulting array should be indexed
  317.      * @param int      $rownum    the row number to fetch (0 = first row)
  318.      *
  319.      * @return mixed  DB_OK on success, NULL when the end of a result set is
  320.      *                  reached or on failure
  321.      *
  322.      * @see DB_result::fetchInto()
  323.      */
  324.     function fetchInto($result&$arr$fetchmode$rownum = null)
  325.     {
  326.         $arr = array();
  327.         if ($rownum !== null{
  328.             $rownum++; // ODBC first row is 1
  329.             if (version_compare(phpversion()'4.2.0''ge')) {
  330.                 $cols @odbc_fetch_into($result$arr$rownum);
  331.             else {
  332.                 $cols @odbc_fetch_into($result$rownum$arr);
  333.             }
  334.         else {
  335.             $cols @odbc_fetch_into($result$arr);
  336.         }
  337.         if (!$cols{
  338.             return null;
  339.         }
  340.         if ($fetchmode !== DB_FETCHMODE_ORDERED{
  341.             for ($i = 0; $i count($arr)$i++{
  342.                 $colName @odbc_field_name($result$i+1);
  343.                 $a[$colName$arr[$i];
  344.             }
  345.             if ($this->options['portability'DB_PORTABILITY_LOWERCASE{
  346.                 $a array_change_key_case($aCASE_LOWER);
  347.             }
  348.             $arr $a;
  349.         }
  350.         if ($this->options['portability'DB_PORTABILITY_RTRIM{
  351.             $this->_rtrimArrayValues($arr);
  352.         }
  353.         if ($this->options['portability'DB_PORTABILITY_NULL_TO_EMPTY{
  354.             $this->_convertNullArrayValuesToEmpty($arr);
  355.         }
  356.         return DB_OK;
  357.     }
  358.  
  359.     // }}}
  360.     // {{{ freeResult()
  361.  
  362.     
  363.     /**
  364.      * Deletes the result set and frees the memory occupied by the result set
  365.      *
  366.      * This method is not meant to be called directly.  Use
  367.      * DB_result::free() instead.  It can't be declared "protected"
  368.      * because DB_result is a separate object.
  369.      *
  370.      * @param resource $result  PHP's query result resource
  371.      *
  372.      * @return bool  TRUE on success, FALSE if $result is invalid
  373.      *
  374.      * @see DB_result::free()
  375.      */
  376.     function freeResult($result)
  377.     {
  378.         return is_resource($result? odbc_free_result($result: false;
  379.     }
  380.  
  381.     // }}}
  382.     // {{{ numCols()
  383.  
  384.     
  385.     /**
  386.      * Gets the number of columns in a result set
  387.      *
  388.      * This method is not meant to be called directly.  Use
  389.      * DB_result::numCols() instead.  It can't be declared "protected"
  390.      * because DB_result is a separate object.
  391.      *
  392.      * @param resource $result  PHP's query result resource
  393.      *
  394.      * @return int  the number of columns.  A DB_Error object on failure.
  395.      *
  396.      * @see DB_result::numCols()
  397.      */
  398.     function numCols($result)
  399.     {
  400.         $cols @odbc_num_fields($result);
  401.         if (!$cols{
  402.             return $this->odbcRaiseError();
  403.         }
  404.         return $cols;
  405.     }
  406.  
  407.     // }}}
  408.     // {{{ affectedRows()
  409.  
  410.     
  411.     /**
  412.      * Determines the number of rows affected by a data maniuplation query
  413.      *
  414.      * 0 is returned for queries that don't manipulate data.
  415.      *
  416.      * @return int  the number of rows.  A DB_Error object on failure.
  417.      */
  418.     function affectedRows()
  419.     {
  420.         if (empty($this->affected)) {  // In case of SELECT stms
  421.             return 0;
  422.         }
  423.         $nrows @odbc_num_rows($this->affected);
  424.         if ($nrows == -1{
  425.             return $this->odbcRaiseError();
  426.         }
  427.         return $nrows;
  428.     }
  429.  
  430.     // }}}
  431.     // {{{ numRows()
  432.  
  433.     
  434.     /**
  435.      * Gets the number of rows in a result set
  436.      *
  437.      * Not all ODBC drivers support this functionality.  If they don't
  438.      * a DB_Error object for DB_ERROR_UNSUPPORTED is returned.
  439.      *
  440.      * This method is not meant to be called directly.  Use
  441.      * DB_result::numRows() instead.  It can't be declared "protected"
  442.      * because DB_result is a separate object.
  443.      *
  444.      * @param resource $result  PHP's query result resource
  445.      *
  446.      * @return int  the number of rows.  A DB_Error object on failure.
  447.      *
  448.      * @see DB_result::numRows()
  449.      */
  450.     function numRows($result)
  451.     {
  452.         $nrows @odbc_num_rows($result);
  453.         if ($nrows == -1{
  454.             return $this->odbcRaiseError(DB_ERROR_UNSUPPORTED);
  455.         }
  456.         if ($nrows === false{
  457.             return $this->odbcRaiseError();
  458.         }
  459.         return $nrows;
  460.     }
  461.  
  462.     // }}}
  463.     // {{{ quoteIdentifier()
  464.  
  465.     
  466.     /**
  467.      * Quotes a string so it can be safely used as a table or column name
  468.      *
  469.      * Use 'mssql' as the dbsyntax in the DB DSN only if you've unchecked
  470.      * "Use ANSI quoted identifiers" when setting up the ODBC data source.
  471.      *
  472.      * @param string $str  identifier name to be quoted
  473.      *
  474.      * @return string  quoted identifier string
  475.      *
  476.      * @see DB_common::quoteIdentifier()
  477.      * @since Method available since Release 1.6.0
  478.      */
  479.     function quoteIdentifier($str)
  480.     {
  481.         switch ($this->dsn['dbsyntax']{
  482.             case 'access':
  483.                 return '[' $str ']';
  484.             case 'mssql':
  485.             case 'sybase':
  486.                 return '[' str_replace(']'']]'$str']';
  487.             case 'mysql':
  488.             case 'mysqli':
  489.                 return '`' $str '`';
  490.             default:
  491.                 return '"' str_replace('"''""'$str'"';
  492.         }
  493.     }
  494.  
  495.     // }}}
  496.     // {{{ quote()
  497.  
  498.     
  499.     /**
  500.      * @deprecated  Deprecated in release 1.6.0
  501.      * @internal
  502.      */
  503.     function quote($str)
  504.     {
  505.         return $this->quoteSmart($str);
  506.     }
  507.  
  508.     // }}}
  509.     // {{{ nextId()
  510.  
  511.     
  512.     /**
  513.      * Returns the next free id in a sequence
  514.      *
  515.      * @param string  $seq_name  name of the sequence
  516.      * @param boolean $ondemand  when true, the seqence is automatically
  517.      *                             created if it does not exist
  518.      *
  519.      * @return int  the next id number in the sequence.
  520.      *                A DB_Error object on failure.
  521.      *
  522.      * @see DB_common::nextID(), DB_common::getSequenceName(),
  523.      *       DB_odbc::createSequence(), DB_odbc::dropSequence()
  524.      */
  525.     function nextId($seq_name$ondemand = true)
  526.     {
  527.         $seqname $this->getSequenceName($seq_name);
  528.         $repeat = 0;
  529.         do {
  530.             $this->pushErrorHandling(PEAR_ERROR_RETURN);
  531.             $result $this->query("update ${seqname} set id = id + 1");
  532.             $this->popErrorHandling();
  533.             if ($ondemand && DB::isError($result&&
  534.                 $result->getCode(== DB_ERROR_NOSUCHTABLE{
  535.                 $repeat = 1;
  536.                 $this->pushErrorHandling(PEAR_ERROR_RETURN);
  537.                 $result $this->createSequence($seq_name);
  538.                 $this->popErrorHandling();
  539.                 if (DB::isError($result)) {
  540.                     return $this->raiseError($result);
  541.                 }
  542.                 $result $this->query("insert into ${seqname} (id) values(0)");
  543.             else {
  544.                 $repeat = 0;
  545.             }
  546.         while ($repeat);
  547.  
  548.         if (DB::isError($result)) {
  549.             return $this->raiseError($result);
  550.         }
  551.  
  552.         $result $this->query("select id from ${seqname}");
  553.         if (DB::isError($result)) {
  554.             return $result;
  555.         }
  556.  
  557.         $row $result->fetchRow(DB_FETCHMODE_ORDERED);
  558.         if (DB::isError($row || !$row)) {
  559.             return $row;
  560.         }
  561.  
  562.         return $row[0];
  563.     }
  564.  
  565.     /**
  566.      * Creates a new sequence
  567.      *
  568.      * @param string $seq_name  name of the new sequence
  569.      *
  570.      * @return int  DB_OK on success.  A DB_Error object on failure.
  571.      *
  572.      * @see DB_common::createSequence(), DB_common::getSequenceName(),
  573.      *       DB_odbc::nextID(), DB_odbc::dropSequence()
  574.      */
  575.     function createSequence($seq_name)
  576.     {
  577.         return $this->query('CREATE TABLE '
  578.                             . $this->getSequenceName($seq_name)
  579.                             . ' (id integer NOT NULL,'
  580.                             . ' PRIMARY KEY(id))');
  581.     }
  582.  
  583.     // }}}
  584.     // {{{ dropSequence()
  585.  
  586.     
  587.     /**
  588.      * Deletes a sequence
  589.      *
  590.      * @param string $seq_name  name of the sequence to be deleted
  591.      *
  592.      * @return int  DB_OK on success.  A DB_Error object on failure.
  593.      *
  594.      * @see DB_common::dropSequence(), DB_common::getSequenceName(),
  595.      *       DB_odbc::nextID(), DB_odbc::createSequence()
  596.      */
  597.     function dropSequence($seq_name)
  598.     {
  599.         return $this->query('DROP TABLE ' $this->getSequenceName($seq_name));
  600.     }
  601.  
  602.     // }}}
  603.     // {{{ autoCommit()
  604.  
  605.     
  606.     /**
  607.      * Enables or disables automatic commits
  608.      *
  609.      * @param bool $onoff  true turns it on, false turns it off
  610.      *
  611.      * @return int  DB_OK on success.  A DB_Error object if the driver
  612.      *                doesn't support auto-committing transactions.
  613.      */
  614.     function autoCommit($onoff = false)
  615.     {
  616.         if (!@odbc_autocommit($this->connection$onoff)) {
  617.             return $this->odbcRaiseError();
  618.         }
  619.         return DB_OK;
  620.     }
  621.  
  622.     // }}}
  623.     // {{{ commit()
  624.  
  625.     
  626.     /**
  627.      * Commits the current transaction
  628.      *
  629.      * @return int  DB_OK on success.  A DB_Error object on failure.
  630.      */
  631.     function commit()
  632.     {
  633.         if (!@odbc_commit($this->connection)) {
  634.             return $this->odbcRaiseError();
  635.         }
  636.         return DB_OK;
  637.     }
  638.  
  639.     // }}}
  640.     // {{{ rollback()
  641.  
  642.     
  643.     /**
  644.      * Reverts the current transaction
  645.      *
  646.      * @return int  DB_OK on success.  A DB_Error object on failure.
  647.      */
  648.     function rollback()
  649.     {
  650.         if (!@odbc_rollback($this->connection)) {
  651.             return $this->odbcRaiseError();
  652.         }
  653.         return DB_OK;
  654.     }
  655.  
  656.     // }}}
  657.     // {{{ odbcRaiseError()
  658.  
  659.     
  660.     /**
  661.      * Produces a DB_Error object regarding the current problem
  662.      *
  663.      * @param int $errno  if the error is being manually raised pass a
  664.      *                      DB_ERROR* constant here.  If this isn't passed
  665.      *                      the error information gathered from the DBMS.
  666.      *
  667.      * @return object  the DB_Error object
  668.      *
  669.      * @see DB_common::raiseError(),
  670.      *       DB_odbc::errorNative(), DB_common::errorCode()
  671.      */
  672.     function odbcRaiseError($errno = null)
  673.     {
  674.         if ($errno === null{
  675.             switch ($this->dbsyntax{
  676.                 case 'access':
  677.                     if ($this->options['portability'DB_PORTABILITY_ERRORS{
  678.                         $this->errorcode_map['07001'DB_ERROR_NOSUCHFIELD;
  679.                     else {
  680.                         // Doing this in case mode changes during runtime.
  681.                         $