1<?php
2/**
3 * Zend Framework
4 *
5 * LICENSE
6 *
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
14 *
15 * @category   Zend
16 * @package    Zend_Db
17 * @subpackage Adapter
18 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
19 * @license    http://framework.zend.com/license/new-bsd     New BSD License
20 * @version    $Id: Ibm.php 23775 2011-03-01 17:25:24Z ralph $
21 */
22
23
24/** @see Zend_Db_Adapter_Pdo_Abstract */
25// require_once 'Zend/Db/Adapter/Pdo/Abstract.php';
26
27/** @see Zend_Db_Abstract_Pdo_Ibm_Db2 */
28// require_once 'Zend/Db/Adapter/Pdo/Ibm/Db2.php';
29
30/** @see Zend_Db_Abstract_Pdo_Ibm_Ids */
31// require_once 'Zend/Db/Adapter/Pdo/Ibm/Ids.php';
32
33/** @see Zend_Db_Statement_Pdo_Ibm */
34// require_once 'Zend/Db/Statement/Pdo/Ibm.php';
35
36
37/**
38 * @category   Zend
39 * @package    Zend_Db
40 * @subpackage Adapter
41 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
42 * @license    http://framework.zend.com/license/new-bsd     New BSD License
43 */
44class Zend_Db_Adapter_Pdo_Ibm extends Zend_Db_Adapter_Pdo_Abstract
45{
46    /**
47     * PDO type.
48     *
49     * @var string
50     */
51    protected $_pdoType = 'ibm';
52
53    /**
54     * The IBM data server connected to
55     *
56     * @var string
57     */
58    protected $_serverType = null;
59
60    /**
61     * Keys are UPPERCASE SQL datatypes or the constants
62     * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
63     *
64     * Values are:
65     * 0 = 32-bit integer
66     * 1 = 64-bit integer
67     * 2 = float or decimal
68     *
69     * @var array Associative array of datatypes to values 0, 1, or 2.
70     */
71    protected $_numericDataTypes = array(
72                        Zend_Db::INT_TYPE    => Zend_Db::INT_TYPE,
73                        Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
74                        Zend_Db::FLOAT_TYPE  => Zend_Db::FLOAT_TYPE,
75                        'INTEGER'            => Zend_Db::INT_TYPE,
76                        'SMALLINT'           => Zend_Db::INT_TYPE,
77                        'BIGINT'             => Zend_Db::BIGINT_TYPE,
78                        'DECIMAL'            => Zend_Db::FLOAT_TYPE,
79                        'DEC'                => Zend_Db::FLOAT_TYPE,
80                        'REAL'               => Zend_Db::FLOAT_TYPE,
81                        'NUMERIC'            => Zend_Db::FLOAT_TYPE,
82                        'DOUBLE PRECISION'   => Zend_Db::FLOAT_TYPE,
83                        'FLOAT'              => Zend_Db::FLOAT_TYPE
84                        );
85
86    /**
87     * Creates a PDO object and connects to the database.
88     *
89     * The IBM data server is set.
90     * Current options are DB2 or IDS
91     * @todo also differentiate between z/OS and i/5
92     *
93     * @return void
94     * @throws Zend_Db_Adapter_Exception
95     */
96    public function _connect()
97    {
98        if ($this->_connection) {
99            return;
100        }
101        parent::_connect();
102
103        $this->getConnection()->setAttribute(Zend_Db::ATTR_STRINGIFY_FETCHES, true);
104
105        try {
106            if ($this->_serverType === null) {
107                $server = substr($this->getConnection()->getAttribute(PDO::ATTR_SERVER_INFO), 0, 3);
108
109                switch ($server) {
110                    case 'DB2':
111                        $this->_serverType = new Zend_Db_Adapter_Pdo_Ibm_Db2($this);
112
113                        // Add DB2-specific numeric types
114                        $this->_numericDataTypes['DECFLOAT'] = Zend_Db::FLOAT_TYPE;
115                        $this->_numericDataTypes['DOUBLE']   = Zend_Db::FLOAT_TYPE;
116                        $this->_numericDataTypes['NUM']      = Zend_Db::FLOAT_TYPE;
117
118                        break;
119                    case 'IDS':
120                        $this->_serverType = new Zend_Db_Adapter_Pdo_Ibm_Ids($this);
121
122                        // Add IDS-specific numeric types
123                        $this->_numericDataTypes['SERIAL']       = Zend_Db::INT_TYPE;
124                        $this->_numericDataTypes['SERIAL8']      = Zend_Db::BIGINT_TYPE;
125                        $this->_numericDataTypes['INT8']         = Zend_Db::BIGINT_TYPE;
126                        $this->_numericDataTypes['SMALLFLOAT']   = Zend_Db::FLOAT_TYPE;
127                        $this->_numericDataTypes['MONEY']        = Zend_Db::FLOAT_TYPE;
128
129                        break;
130                    }
131            }
132        } catch (PDOException $e) {
133            /** @see Zend_Db_Adapter_Exception */
134            // require_once 'Zend/Db/Adapter/Exception.php';
135            $error = strpos($e->getMessage(), 'driver does not support that attribute');
136            if ($error) {
137                throw new Zend_Db_Adapter_Exception("PDO_IBM driver extension is downlevel.  Please use driver release version 1.2.1 or later", 0, $e);
138            } else {
139                throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
140            }
141        }
142    }
143
144    /**
145     * Creates a PDO DSN for the adapter from $this->_config settings.
146     *
147     * @return string
148     */
149    protected function _dsn()
150    {
151        $this->_checkRequiredOptions($this->_config);
152
153        // check if using full connection string
154        if (array_key_exists('host', $this->_config)) {
155            $dsn = ';DATABASE=' . $this->_config['dbname']
156            . ';HOSTNAME=' . $this->_config['host']
157            . ';PORT='     . $this->_config['port']
158            // PDO_IBM supports only DB2 TCPIP protocol
159            . ';PROTOCOL=' . 'TCPIP;';
160        } else {
161            // catalogued connection
162            $dsn = $this->_config['dbname'];
163        }
164        return $this->_pdoType . ': ' . $dsn;
165    }
166
167    /**
168     * Checks required options
169     *
170     * @param  array $config
171     * @throws Zend_Db_Adapter_Exception
172     * @return void
173     */
174    protected function _checkRequiredOptions(array $config)
175    {
176        parent::_checkRequiredOptions($config);
177
178        if (array_key_exists('host', $this->_config) &&
179        !array_key_exists('port', $config)) {
180            /** @see Zend_Db_Adapter_Exception */
181            // require_once 'Zend/Db/Adapter/Exception.php';
182            throw new Zend_Db_Adapter_Exception("Configuration must have a key for 'port' when 'host' is specified");
183        }
184    }
185
186    /**
187     * Prepares an SQL statement.
188     *
189     * @param string $sql The SQL statement with placeholders.
190     * @param array $bind An array of data to bind to the placeholders.
191     * @return PDOStatement
192     */
193    public function prepare($sql)
194    {
195        $this->_connect();
196        $stmtClass = $this->_defaultStmtClass;
197        $stmt = new $stmtClass($this, $sql);
198        $stmt->setFetchMode($this->_fetchMode);
199        return $stmt;
200    }
201
202    /**
203     * Returns a list of the tables in the database.
204     *
205     * @return array
206     */
207    public function listTables()
208    {
209        $this->_connect();
210        return $this->_serverType->listTables();
211    }
212
213    /**
214     * Returns the column descriptions for a table.
215     *
216     * The return value is an associative array keyed by the column name,
217     * as returned by the RDBMS.
218     *
219     * The value of each array element is an associative array
220     * with the following keys:
221     *
222     * SCHEMA_NAME      => string; name of database or schema
223     * TABLE_NAME       => string;
224     * COLUMN_NAME      => string; column name
225     * COLUMN_POSITION  => number; ordinal position of column in table
226     * DATA_TYPE        => string; SQL datatype name of column
227     * DEFAULT          => string; default expression of column, null if none
228     * NULLABLE         => boolean; true if column can have nulls
229     * LENGTH           => number; length of CHAR/VARCHAR
230     * SCALE            => number; scale of NUMERIC/DECIMAL
231     * PRECISION        => number; precision of NUMERIC/DECIMAL
232     * UNSIGNED         => boolean; unsigned property of an integer type
233     * PRIMARY          => boolean; true if column is part of the primary key
234     * PRIMARY_POSITION => integer; position of column in primary key
235     *
236     * @todo Discover integer unsigned property.
237     *
238     * @param string $tableName
239     * @param string $schemaName OPTIONAL
240     * @return array
241     */
242    public function describeTable($tableName, $schemaName = null)
243    {
244        $this->_connect();
245        return $this->_serverType->describeTable($tableName, $schemaName);
246    }
247
248    /**
249     * Inserts a table row with specified data.
250     * Special handling for PDO_IBM
251     * remove empty slots
252     *
253     * @param mixed $table The table to insert data into.
254     * @param array $bind Column-value pairs.
255     * @return int The number of affected rows.
256     */
257    public function insert($table, array $bind)
258    {
259        $this->_connect();
260        $newbind = array();
261        if (is_array($bind)) {
262            foreach ($bind as $name => $value) {
263                if($value !== null) {
264                    $newbind[$name] = $value;
265                }
266            }
267        }
268
269        return parent::insert($table, $newbind);
270    }
271
272    /**
273     * Adds an adapter-specific LIMIT clause to the SELECT statement.
274     *
275     * @param string $sql
276     * @param integer $count
277     * @param integer $offset OPTIONAL
278     * @return string
279     */
280    public function limit($sql, $count, $offset = 0)
281    {
282       $this->_connect();
283       return $this->_serverType->limit($sql, $count, $offset);
284    }
285
286    /**
287     * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT
288     * column.
289     *
290     * @param string $tableName OPTIONAL
291     * @param string $primaryKey OPTIONAL
292     * @return integer
293     */
294    public function lastInsertId($tableName = null, $primaryKey = null)
295    {
296        $this->_connect();
297
298         if ($tableName !== null) {
299            $sequenceName = $tableName;
300            if ($primaryKey) {
301                $sequenceName .= "_$primaryKey";
302            }
303            $sequenceName .= '_seq';
304            return $this->lastSequenceId($sequenceName);
305        }
306
307        $id = $this->getConnection()->lastInsertId();
308
309        return $id;
310    }
311
312    /**
313     * Return the most recent value from the specified sequence in the database.
314     *
315     * @param string $sequenceName
316     * @return integer
317     */
318    public function lastSequenceId($sequenceName)
319    {
320        $this->_connect();
321        return $this->_serverType->lastSequenceId($sequenceName);
322    }
323
324    /**
325     * Generate a new value from the specified sequence in the database,
326     * and return it.
327     *
328     * @param string $sequenceName
329     * @return integer
330     */
331    public function nextSequenceId($sequenceName)
332    {
333        $this->_connect();
334        return $this->_serverType->nextSequenceId($sequenceName);
335    }
336
337    /**
338     * Retrieve server version in PHP style
339     * Pdo_Idm doesn't support getAttribute(PDO::ATTR_SERVER_VERSION)
340     * @return string
341     */
342    public function getServerVersion()
343    {
344        try {
345            $stmt = $this->query('SELECT service_level, fixpack_num FROM TABLE (sysproc.env_get_inst_info()) as INSTANCEINFO');
346            $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
347            if (count($result)) {
348                $matches = null;
349                if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $result[0][0], $matches)) {
350                    return $matches[1];
351                } else {
352                    return null;
353                }
354            }
355            return null;
356        } catch (PDOException $e) {
357            return null;
358        }
359    }
360}
361