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