dbo_db2.php

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

Generated on Sun Nov 22 00:30:53 2009 for CakePHP 1.2.x.x (v1.2.4.8284) by doxygen 1.4.7