dbo_db2.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: dbo__db2_8php-source.html 580 2008-07-01 14:45:49Z gwoo $ */
00003 /**
00004  * IBM DB2 for DBO
00005  *
00006  * This file supports IBM DB2 and Cloudscape (aka Apache Derby,
00007  * Sun Java DB) using the native ibm_db2 extension:
00008  * http://pecl.php.net/package/ibm_db2
00009  *
00010  * PHP versions 4 and 5
00011  *
00012  * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>
00013  * Copyright 2007, Cake Software Foundation, Inc.
00014  *                              1785 E. Sahara Avenue, Suite 490-204
00015  *                              Las Vegas, Nevada 89104
00016  *
00017  * Licensed under The MIT License
00018  * Redistributions of files must retain the above copyright notice.
00019  *
00020  * @filesource
00021  * @copyright       Copyright 2005-2008, Cake Software Foundation, Inc.
00022  * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00023  * @package         cake
00024  * @subpackage      cake.cake.libs.model.datasources.dbo
00025  * @since           CakePHP(tm) v 0.10.5.1790
00026  * @version         $Revision: 580 $
00027  * @modifiedby      $LastChangedBy: gwoo $
00028  * @lastmodified    $Date: 2008-07-01 09:45:49 -0500 (Tue, 01 Jul 2008) $
00029  * @license         http://www.opensource.org/licenses/mit-license.php The MIT License
00030  */
00031 /**
00032  * IBM DB2 for DBO
00033  *
00034  * This file supports IBM DB2 and Cloudscape (aka Apache Derby,
00035  * Sun Java DB) using the native ibm_db2 extension:
00036  * http://pecl.php.net/package/ibm_db2
00037  *
00038  * @package     cake
00039  * @subpackage  cake.cake.libs.model.datasources.dbo
00040  */
00041 class DboDb2 extends DboSource {
00042 /**
00043  * A short description of the type of driver.
00044  *
00045  * @var string
00046  */
00047     var $description = 'IBM DB2 DBO Driver';
00048 /**
00049  * The start quote in which database column and table names should be wrapped.
00050  *
00051  * @var string
00052  */
00053     var $startQuote = '';
00054 /**
00055  * The end quote in which database column and table names should be wrapped.
00056  *
00057  * @var string
00058  */
00059     var $endQuote = '';
00060 /**
00061  * An array of base configuration settings to be used if settings are not
00062  * provided, i.e. default host, port, and connection method.
00063  *
00064  * @var array
00065  */
00066     var $_baseConfig = array(
00067         'persistent'    => true,
00068         'login'         => 'db2inst1',
00069         'password'      => '',
00070         'database'      => 'cake',
00071         'schema'        => '',
00072         'hostname'      => '127.0.0.1',
00073         'port'          => '50001',
00074         'encoding'      => 'UTF-8',
00075         'cataloged'     => true,
00076         'autocommit'    => true
00077     );
00078 /**
00079  * An array that maps Cake column types to database native column types.
00080  * The mapped information can include a reference to a function that should
00081  * be used to format the data, as well as a string that defines the
00082  * formatting according to that function.
00083  *
00084  * @var array
00085  */
00086     var $columns = array(
00087         'primary_key'   => array('name' => 'not null generated by default as identity (start with 1, increment by 1)'),
00088         'string'        => array('name' => 'varchar', 'limit' => '255'),
00089         'text'          => array('name' => 'clob'),
00090         'integer'       => array('name' => 'integer', 'limit' => '10', 'formatter' => 'intval'),
00091         'float'         => array('name' => 'double', 'formatter' => 'floatval'),
00092         'datetime'      => array('name' => 'timestamp', 'format' => 'Y-m-d-H.i.s', 'formatter' => 'date'),
00093         'timestamp'     => array('name' => 'timestamp', 'format' => 'Y-m-d-H.i.s', 'formatter' => 'date'),
00094         'time'          => array('name' => 'time', 'format' => 'H.i.s', 'formatter' => 'date'),
00095         'date'          => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'),
00096         'binary'        => array('name' => 'blob'),
00097         'boolean'       => array('name' => 'smallint', 'limit' => '1')
00098     );
00099 /**
00100  * A map for every result mapping tables to columns
00101  *
00102  * @var array result -> ( table -> column )
00103  */
00104     var $_resultMap = array();
00105 /**
00106  * Connects to the database using options in the given configuration array.
00107  *
00108  * @return boolean True if the database could be connected, else false
00109  */
00110     function connect() {
00111         $config = $this->config;
00112         $connect = 'db2_connect';
00113         if ($config['persistent']) {
00114             $connect = 'db2_pconnect';
00115         }
00116         $this->connected = false;
00117 
00118         if ($config['cataloged']) {
00119             $this->connection = $connect($config['database'], $config['login'], $config['password']);
00120         } else {
00121             $conn_string = sprintf(
00122                 "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;",
00123                 $config['database'],
00124                 $config['hostname'],
00125                 $config['port'],
00126                 $config['login'],
00127                 $config['password']
00128             );
00129             $this->connection = db2_connect($conn_string, '', '');
00130         }
00131 
00132         if ($this->connection) {
00133             $this->connected = true;
00134         }
00135 
00136         if ($config['schema'] !== '') {
00137             $this->_execute('SET CURRENT SCHEMA = ' . $config['schema']);
00138         }
00139         return $this->connected;
00140     }
00141 /**
00142  * Disconnects from database.
00143  *
00144  * @return boolean True if the database could be disconnected, else false
00145  */
00146     function disconnect() {
00147         @db2_free_result($this->results);
00148         $this->connected = !@db2_close($this->connection);
00149         return !$this->connected;
00150     }
00151 /**
00152  * Executes given SQL statement.  We should use prepare / execute to allow the
00153  * database server to reuse its access plan and increase the efficiency
00154  * of your database access
00155  *
00156  * @param string $sql SQL statement
00157  * @return resource Result resource identifier
00158  * @access protected
00159  */
00160     function _execute($sql) {
00161         // get result from db
00162         $result = db2_exec($this->connection, $sql);
00163 
00164         if(!is_bool($result)){
00165             // build table/column map for this result
00166             $map = array();
00167             $num_fields = db2_num_fields($result);
00168             $index = 0;
00169             $j = 0;
00170             $offset = 0;
00171 
00172             while ($j < $num_fields) {
00173                 $columnName = strtolower(db2_field_name($result, $j));
00174                 $tmp = strpos($sql, '.' . $columnName, $offset);
00175                 $tableName = substr($sql, $offset, ($tmp-$offset));
00176                 $tableName = substr($tableName, strrpos($tableName, ' ') + 1);
00177                 $map[$index++] = array($tableName, $columnName);
00178                 $j++;
00179                 $offset = strpos($sql, ' ', $tmp);
00180             }
00181 
00182             $this->_resultMap[$result] = $map;
00183         }
00184 
00185         return $result;
00186     }
00187 /**
00188  * Returns an array of all the tables in the database.
00189  * Should call parent::listSources twice in the method:
00190  * once to see if the list is cached, and once to cache
00191  * the list if not.
00192  *
00193  * @return array Array of tablenames in the database
00194  */
00195     function listSources() {
00196         $cache = parent::listSources();
00197 
00198         if ($cache != null) {
00199             return $cache;
00200         }
00201         $result = db2_tables($this->connection);
00202         $tables = array();
00203 
00204         while (db2_fetch_row($result)) {
00205             $tables[] = strtolower(db2_result($result, 'TABLE_NAME'));
00206         }
00207         parent::listSources($tables);
00208         return $tables;
00209     }
00210 /**
00211  * Returns an array of the fields in given table name.
00212  *
00213  * @param Model $model Model object to describe
00214  * @return array Fields in table. Keys are name and type
00215  */
00216     function &describe(&$model) {
00217         $cache = parent::describe($model);
00218 
00219         if ($cache != null) {
00220             return $cache;
00221         }
00222         $fields = array();
00223         $result = db2_columns($this->connection, '', '', strtoupper($this->fullTableName($model)));
00224 
00225         while (db2_fetch_row($result)) {
00226             $fields[strtolower(db2_result($result, 'COLUMN_NAME'))] = array(
00227                 'type' => $this->column(strtolower(db2_result($result, 'TYPE_NAME'))),
00228                 'null' => db2_result($result, 'NULLABLE'),
00229                 'default' => db2_result($result, 'COLUMN_DEF'),
00230                 'length' => db2_result($result, 'COLUMN_SIZE')
00231             );
00232         }
00233         $this->__cacheDescription($model->tablePrefix . $model->table, $fields);
00234         return $fields;
00235     }
00236 /**
00237  * Returns a quoted name of $data for use in an SQL statement.
00238  *
00239  * @param string $data Name (table.field) to be prepared for use in an SQL statement
00240  * @return string Quoted for MySQL
00241  */
00242     function name($data) {
00243         return $data;
00244     }
00245 /**
00246  * Returns a quoted and escaped string of $data for use in an SQL statement.
00247  *
00248  * @param string $data String to be prepared for use in an SQL statement
00249  * @param string $column The column into which this data will be inserted
00250  * @return string Quoted and escaped
00251  * @todo Add logic that formats/escapes data based on column type
00252  */
00253 
00254     function value($data, $column = null, $safe = false) {
00255         $parent = parent::value($data, $column, $safe);
00256 
00257         if ($parent != null) {
00258             return $parent;
00259         }
00260 
00261         if ($data === null) {
00262             return 'NULL';
00263         }
00264 
00265         if ($data === '') {
00266             return  "''";
00267         }
00268 
00269         switch ($column) {
00270             case 'boolean':
00271                 $data = $this->boolean((bool)$data);
00272             break;
00273             case 'integer':
00274                 $data = intval($data);
00275             break;
00276             default:
00277                 $data = str_replace("'", "''", $data);
00278             break;
00279         }
00280 
00281         // TODO: Make sure this isn't catching other numeric values
00282         // that are going into string columns.
00283         if ($column == 'integer' || is_numeric($data)) {
00284             return $data;
00285         } else {
00286             return "'" . $data . "'";
00287         }
00288     }
00289 /**
00290  * Not sure about this one, MySQL needs it but does ODBC?  Safer just to leave it
00291  * Translates between PHP boolean values and MySQL (faked) boolean values
00292  *
00293  * @param mixed $data Value to be translated
00294  * @return mixed Converted boolean value
00295  */
00296     function boolean($data) {
00297         if ($data === true || $data === false) {
00298             if ($data === true) {
00299                 return 1;
00300             }
00301             return 0;
00302         } else {
00303             if (intval($data !== 0)) {
00304                 return true;
00305             }
00306             return false;
00307         }
00308     }
00309 /**
00310  * Begins a transaction.  Returns true if the transaction was
00311  * started successfully, otherwise false.
00312  *
00313  * @param unknown_type $model
00314  * @return boolean True on success, false on fail
00315  * (i.e. if the database/model does not support transactions).
00316  */
00317     function begin(&$model) {
00318         if (parent::begin($model)) {
00319             if (db2_autocommit($this->connection, DB2_AUTOCOMMIT_OFF)) {
00320                 $this->_transactionStarted = true;
00321                 return true;
00322             }
00323         }
00324         return false;
00325     }
00326 /**
00327  * Commit a transaction
00328  *
00329  * @param unknown_type $model
00330  * @return boolean True on success, false on fail
00331  * (i.e. if the database/model does not support transactions,
00332  * or a transaction has not started).
00333  */
00334     function commit(&$model) {
00335         if (parent::commit($model)) {
00336             if (db2_commit($this->connection)) {
00337                 $this->_transactionStarted = false;
00338                 db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON);
00339                 return true;
00340             }
00341         }
00342         return false;
00343     }
00344 /**
00345  * Rollback a transaction
00346  *
00347  * @param unknown_type $model
00348  * @return boolean True on success, false on fail
00349  * (i.e. if the database/model does not support transactions,
00350  * or a transaction has not started).
00351  */
00352     function rollback(&$model) {
00353         if (parent::rollback($model)) {
00354             $this->_transactionStarted = false;
00355             db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON);
00356             return db2_rollback($this->connection);
00357         }
00358         return false;
00359     }
00360 /**
00361  * Removes Identity (primary key) column from update data before returning to parent
00362  *
00363  * @param Model $model
00364  * @param array $fields
00365  * @param array $values
00366  * @return array
00367  */
00368     function update(&$model, $fields = array(), $values = array()) {
00369         foreach ($fields as $i => $field) {
00370             if ($field == $model->primaryKey) {
00371                 unset ($fields[$i]);
00372                 unset ($values[$i]);
00373                 break;
00374             }
00375         }
00376         return parent::update($model, $fields, $values);
00377     }
00378 /**
00379  * Returns a formatted error message from previous database operation.
00380  * DB2 distinguishes between statement and connnection errors so we
00381  * must check for both.
00382  *
00383  * @return string Error message with error number
00384  */
00385     function lastError() {
00386         if (db2_stmt_error()) {
00387             return db2_stmt_error() . ': ' . db2_stmt_errormsg();
00388         } elseif (db2_conn_error()) {
00389             return db2_conn_error() . ': ' . db2_conn_errormsg();
00390         }
00391         return null;
00392     }
00393 /**
00394  * Returns number of affected rows in previous database operation. If no previous operation exists,
00395  * this returns false.
00396  *
00397  * @return integer Number of affected rows
00398  */
00399     function lastAffected() {
00400         if ($this->_result) {
00401             return db2_num_rows($this->_result);
00402         }
00403         return null;
00404     }
00405 /**
00406  * Returns number of rows in previous resultset. If no previous resultset exists,
00407  * this returns false.
00408  *
00409  * @return integer Number of rows in resultset
00410  */
00411     function lastNumRows() {
00412         if ($this->_result) {
00413             return db2_num_rows($this->_result);
00414         }
00415         return null;
00416     }
00417 /**
00418  * Returns the ID generated from the previous INSERT operation.
00419  *
00420  * @param unknown_type $source
00421  * @return in
00422  */
00423     function lastInsertId($source = null) {
00424         $data = $this->fetchRow(sprintf('SELECT SYSIBM.IDENTITY_VAL_LOCAL() AS ID FROM %s FETCH FIRST ROW ONLY', $source));
00425 
00426         if ($data && isset($data[0]['id'])) {
00427             return $data[0]['id'];
00428         }
00429         return null;
00430     }
00431 /**
00432  * Returns a limit statement in the correct format for the particular database.
00433  *
00434  * @param integer $limit Limit of results returned
00435  * @param integer $offset Offset from which to start results
00436  * @return string SQL limit/offset statement
00437  */
00438     function limit($limit, $offset = null) {
00439         if ($limit) {
00440             $rt = '';
00441 
00442             // If limit is not in the passed value already, add a limit clause.
00443             if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) {
00444                 $rt = sprintf('FETCH FIRST %d ROWS ONLY', $limit);
00445             }
00446 
00447             // TODO: Implement paging with the offset.  This could get hairy.
00448             /*
00449             WITH WHOLE AS
00450             (SELECT FIRSTNME, MIDINIT, LASTNAME, SALARY,
00451             ROW_NUMBER() OVER (ORDER BY SALARY DESC) AS RN
00452             FROM EMPLOYEE)
00453             SELECT FIRSTNME, MIDINIT, LASTNAME, SALARY, RN
00454             FROM WHOLE
00455             WHERE RN BETWEEN 10 AND 15
00456             */
00457 
00458             /*
00459             if ($offset) {
00460                 $rt .= ' ' . $offset . ',';
00461             }
00462 
00463             $rt .= ' ' . $limit;
00464             */
00465 
00466             return $rt;
00467         }
00468         return null;
00469     }
00470 /**
00471  * Converts database-layer column types to basic types
00472  *
00473  * @param string $real Real database-layer column type (i.e. "varchar(255)")
00474  * @return string Abstract column type (i.e. "string")
00475  */
00476     function column($real) {
00477         if (is_array($real)) {
00478             $col = $real['name'];
00479 
00480             if (isset($real['limit'])) {
00481                 $col .= '(' . $real['limit'] . ')';
00482             }
00483             return $col;
00484         }
00485         $col                = str_replace(')', '', $real);
00486         $limit              = null;
00487         if (strpos($col, '(') !== false) {
00488             list($col, $limit) = explode('(', $col);
00489         }
00490 
00491         if (in_array($col, array('date', 'time', 'datetime', 'timestamp'))) {
00492             return $col;
00493         }
00494 
00495         if ($col == 'smallint') {
00496             return 'boolean';
00497         }
00498 
00499         if (strpos($col, 'char') !== false) {
00500             return 'string';
00501         }
00502 
00503         if (strpos($col, 'clob') !== false) {
00504             return 'text';
00505         }
00506 
00507         if (strpos($col, 'blob') !== false || $col == 'image') {
00508             return 'binary';
00509         }
00510 
00511         if (in_array($col, array('double', 'real', 'decimal'))) {
00512             return 'float';
00513         }
00514         return 'text';
00515     }
00516 /**
00517  * Maps a result set to an array so that returned fields are
00518  * grouped by model.  Any calculated fields, or fields that
00519  * do not correspond to a particular model belong under array
00520  * key 0.
00521  *
00522  * 1.  Gets the column headers
00523  *
00524  * Post.id
00525  * Post.title
00526  *
00527  *  [0] => Array
00528  *       (
00529  *           [0] => Post
00530  *           [1] => id
00531  *       )
00532  *
00533  *  [1] => Array
00534  *      (
00535  *          [0] => Post
00536  *          [1] => title
00537  *      )
00538  *
00539  * @param unknown_type $results
00540  */
00541     function resultSet(&$results, $sql = null) {
00542         $this->results =& $results;
00543         $this->map = $this->_resultMap[$this->results];
00544     }
00545 /**
00546  * Fetches the next row from the current result set
00547  * Maps the records in the $result property to the map
00548  * created in resultSet().
00549  *
00550  * 2. Gets the actual values.
00551  *
00552  * @return unknown
00553  */
00554     function fetchResult() {
00555         if ($row = db2_fetch_array($this->results)) {
00556             $resultRow = array();
00557             $i = 0;
00558 
00559             foreach ($row as $index => $field) {
00560                 $table = $this->map[$index][0];
00561                 $column = strtolower($this->map[$index][1]);
00562                 $resultRow[$table][$column] = $row[$index];
00563                 $i++;
00564             }
00565             return $resultRow;
00566         } else {
00567             return false;
00568         }
00569     }
00570 }
00571 ?>