Last active
July 19, 2023 15:20
-
-
Save jonkerw85/02ad0b21d773f11110ff3fcf804811c7 to your computer and use it in GitHub Desktop.
PDO_ODBC DB2 Adapter for Zend Framework 1 & zf1-future
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Zend Framework | |
* | |
* LICENSE | |
* | |
* This source file is subject to the new BSD license that is bundled | |
* with this package in the file LICENSE.txt. | |
* It is also available through the world-wide-web at this URL: | |
* http://framework.zend.com/license/new-bsd | |
* If you did not receive a copy of the license and are unable to | |
* obtain it through the world-wide-web, please send an email | |
* to [email protected] so we can send you a copy immediately. | |
* | |
* @category Zend | |
* @package Zend_Db | |
* @subpackage Adapter | |
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) | |
* @license http://framework.zend.com/license/new-bsd New BSD License | |
* @version $Id$ | |
*/ | |
/** @see Zend_Db_Adapter_Pdo_Abstract */ | |
require_once 'Zend/Db/Adapter/Pdo/Abstract.php'; | |
/** | |
* @category Zend | |
* @package Zend_Db | |
* @subpackage Adapter | |
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) | |
* @license http://framework.zend.com/license/new-bsd New BSD License | |
*/ | |
class Hulst_Db_Adapter_Pdo_Odbc extends Zend_Db_Adapter_Pdo_Abstract | |
{ | |
/** | |
* PDO type. | |
* | |
* @var string | |
*/ | |
protected $_pdoType = 'odbc'; | |
protected $_isI5 = true; | |
/** | |
* Keys are UPPERCASE SQL datatypes or the constants | |
* Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE. | |
* | |
* Values are: | |
* 0 = 32-bit integer | |
* 1 = 64-bit integer | |
* 2 = float or decimal | |
* | |
* @var array Associative array of datatypes to values 0, 1, or 2. | |
*/ | |
protected $_numericDataTypes = [ | |
Zend_Db::INT_TYPE => Zend_Db::INT_TYPE, | |
Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, | |
Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, | |
'INTEGER' => Zend_Db::INT_TYPE, | |
'SMALLINT' => Zend_Db::INT_TYPE, | |
'BIGINT' => Zend_Db::BIGINT_TYPE, | |
'DECIMAL' => Zend_Db::FLOAT_TYPE, | |
'NUMERIC' => Zend_Db::FLOAT_TYPE | |
]; | |
/** | |
* Creates a PDO DSN for the adapter from $this->_config settings. | |
* | |
* @return string | |
* @throws Zend_Db_Adapter_Exception | |
*/ | |
protected function _dsn() | |
{ | |
$dsn = "odbc:Driver=" . $this->_config['dsn'] . | |
";System=" . $this->_config['host'] . | |
";Database=" . $this->_config['dbname'] . | |
";UserID=" . $this->_config['username'] . | |
";Password=" . $this->_config['password'] . | |
";ConnectionType=0" . | |
";DefaultLibraries=" . $this->_config['driver_options']['i5_libl'] . | |
";DefaultPkgLibrary=QGPL;DefaultPackage=A/DEFAULT(IBM),2,0,1,0;ExtendedDynamic=0;AllowUnsupportedChar=1;CCSID=1252"; | |
return $dsn; | |
} | |
/** | |
* Returns a list of the tables in the database. | |
* @param string $schema OPTIONAL | |
* @return array | |
*/ | |
public function listTables($schema = null) | |
{ | |
return []; | |
} | |
/** | |
* Quote a raw string. | |
* | |
* @param string $value Raw string | |
* @return string Quoted string | |
*/ | |
protected function _quote($value) | |
{ | |
if (is_int($value) || is_float($value)) { | |
return $value; | |
} | |
return "'" . str_replace(["'"], ["''"], $value) . "'"; | |
} | |
/** | |
* Returns the column descriptions for a table. | |
* | |
* The return value is an associative array keyed by the column name, | |
* as returned by the RDBMS. | |
* | |
* The value of each array element is an associative array | |
* with the following keys: | |
* | |
* SCHEMA_NAME => string; name of database or schema | |
* TABLE_NAME => string; | |
* COLUMN_NAME => string; column name | |
* COLUMN_POSITION => number; ordinal position of column in table | |
* DATA_TYPE => string; SQL datatype name of column | |
* DEFAULT => string; default expression of column, null if none | |
* NULLABLE => boolean; true if column can have nulls | |
* LENGTH => number; length of CHAR/VARCHAR | |
* SCALE => number; scale of NUMERIC/DECIMAL | |
* PRECISION => number; precision of NUMERIC/DECIMAL | |
* UNSIGNED => boolean; unsigned property of an integer type | |
* DB2 not supports UNSIGNED integer. | |
* PRIMARY => boolean; true if column is part of the primary key | |
* PRIMARY_POSITION => integer; position of column in primary key | |
* IDENTITY => integer; true if column is auto-generated with unique values | |
* | |
* @param string $tableName | |
* @param string $schemaName OPTIONAL | |
* @return array | |
*/ | |
public function describeTable($tableName, $schemaName = null) | |
{ | |
// Ensure the connection is made so that _isI5 is set | |
$this->_connect(); | |
if (!$this->_isI5) { | |
$sql = "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno, | |
c.typename, c.default, c.nulls, c.length, c.scale, | |
c.identity, tc.type AS tabconsttype, k.colseq | |
FROM syscat.columns c | |
LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc | |
ON (k.tabschema = tc.tabschema | |
AND k.tabname = tc.tabname | |
AND tc.type = 'P')) | |
ON (c.tabschema = k.tabschema | |
AND c.tabname = k.tabname | |
AND c.colname = k.colname) | |
WHERE " | |
. $this->quoteInto('UPPER(c.tabname) = UPPER(?)', $tableName); | |
if ($schemaName) { | |
$sql .= $this->quoteInto(' AND UPPER(c.tabschema) = UPPER(?)', $schemaName); | |
} | |
$sql .= " ORDER BY c.colno"; | |
} else { | |
// DB2 On I5 specific query | |
$sql = "SELECT DISTINCT C.TABLE_SCHEMA, C.TABLE_NAME, C.COLUMN_NAME, C.ORDINAL_POSITION, | |
C.DATA_TYPE, C.COLUMN_DEFAULT, C.NULLS ,C.LENGTH, C.SCALE, LEFT(C.IDENTITY,1), | |
LEFT(tc.TYPE, 1) AS tabconsttype, k.COLSEQ | |
FROM QSYS2.SYSCOLUMNS C | |
LEFT JOIN (QSYS2.syskeycst k JOIN QSYS2.SYSCST tc | |
ON (k.TABLE_SCHEMA = tc.TABLE_SCHEMA | |
AND k.TABLE_NAME = tc.TABLE_NAME | |
AND LEFT(tc.type,1) = 'P')) | |
ON (C.TABLE_SCHEMA = k.TABLE_SCHEMA | |
AND C.TABLE_NAME = k.TABLE_NAME | |
AND C.COLUMN_NAME = k.COLUMN_NAME) | |
WHERE " | |
. $this->quoteInto('UPPER(C.TABLE_NAME) = UPPER(?)', $tableName); | |
if ($schemaName) { | |
$sql .= $this->quoteInto(' AND UPPER(C.TABLE_SCHEMA) = UPPER(?)', $schemaName); | |
} | |
$sql .= " ORDER BY C.ORDINAL_POSITION FOR FETCH ONLY"; | |
} | |
$desc = array(); | |
$stmt = $this->query($sql); | |
/** | |
* To avoid case issues, fetch using FETCH_NUM | |
*/ | |
$result = $stmt->fetchAll(Zend_Db::FETCH_NUM); | |
/** | |
* The ordering of columns is defined by the query so we can map | |
* to variables to improve readability | |
*/ | |
$tabschema = 0; | |
$tabname = 1; | |
$colname = 2; | |
$colno = 3; | |
$typename = 4; | |
$default = 5; | |
$nulls = 6; | |
$length = 7; | |
$scale = 8; | |
$identityCol = 9; | |
$tabconstType = 10; | |
$colseq = 11; | |
foreach ($result as $key => $row) { | |
list($primary, $primaryPosition, $identity) = array(false, null, false); | |
if ($row[$tabconstType] == 'P') { | |
$primary = true; | |
$primaryPosition = $row[$colseq]; | |
} | |
/** | |
* In IBM DB2, an column can be IDENTITY | |
* even if it is not part of the PRIMARY KEY. | |
*/ | |
if ($row[$identityCol] == 'Y') { | |
$identity = true; | |
} | |
// only colname needs to be case adjusted | |
$desc[$this->foldCase($row[$colname])] = array( | |
'SCHEMA_NAME' => $this->foldCase($row[$tabschema]), | |
'TABLE_NAME' => $this->foldCase($row[$tabname]), | |
'COLUMN_NAME' => $this->foldCase($row[$colname]), | |
'COLUMN_POSITION' => (!$this->_isI5) ? $row[$colno] + 1 : $row[$colno], | |
'DATA_TYPE' => $row[$typename], | |
'DEFAULT' => $row[$default], | |
'NULLABLE' => (bool) ($row[$nulls] == 'Y'), | |
'LENGTH' => $row[$length], | |
'SCALE' => $row[$scale], | |
'PRECISION' => ($row[$typename] == 'DECIMAL' ? $row[$length] : 0), | |
'UNSIGNED' => false, | |
'PRIMARY' => $primary, | |
'PRIMARY_POSITION' => $primaryPosition, | |
'IDENTITY' => $identity | |
); | |
} | |
return $desc; | |
} | |
/** | |
* Adds an adapter-specific LIMIT clause to the SELECT statement. | |
* | |
* @param string $sql | |
* @param integer $count | |
* @param integer $offset OPTIONAL | |
* @return string | |
*/ | |
public function limit($sql, $count, $offset = 0) | |
{ | |
$count = (int)$count; | |
if ($count <= 0) { | |
/** | |
* @see Zend_Db_Adapter_Db2_Exception | |
*/ | |
require_once 'Zend/Db/Adapter/Db2/Exception.php'; | |
throw new Zend_Db_Adapter_Db2_Exception("LIMIT argument count=$count is not valid"); | |
} | |
$offset = (int)$offset; | |
if ($offset < 0) { | |
/** | |
* @see Zend_Db_Adapter_Db2_Exception | |
*/ | |
require_once 'Zend/Db/Adapter/Db2/Exception.php'; | |
throw new Zend_Db_Adapter_Db2_Exception("LIMIT argument offset=$offset is not valid"); | |
} | |
if ($offset === 0) { | |
return $sql . " FETCH FIRST $count ROWS ONLY"; | |
} | |
/** | |
* DB2 does not implement the LIMIT clause as some RDBMS do. | |
* We have to simulate it with subqueries and ROWNUM. | |
* Unfortunately because we use the column wildcard "*", | |
* this puts an extra column into the query result set. | |
*/ | |
return "SELECT z2.* | |
FROM ( | |
SELECT ROW_NUMBER() OVER() AS \"ZEND_DB_ROWNUM\", z1.* | |
FROM ( | |
" . $sql . " | |
) z1 | |
) z2 | |
WHERE z2.zend_db_rownum BETWEEN " . ($offset + 1) . " AND " . ($offset + $count); | |
} | |
/** | |
* Special handling for PDO query(). | |
* All bind parameter names must begin with ':' | |
* | |
* @param string|Zend_Db_Select $sql The SQL statement with placeholders. | |
* @param array $bind An array of data to bind to the placeholders. | |
* @return PDOStatement|Zend_Db_Statement|Zend_Db_Statement_Interface | |
* @throws Zend_Db_Adapter_Exception To re-throw PDOException. | |
*/ | |
public function query($sql, $bind = []) | |
{ | |
if (empty($bind) && $sql instanceof Zend_Db_Select) { | |
$bind = $sql->getBind(); | |
} | |
if (is_array($bind)) { | |
foreach ($bind as $name => $value) { | |
if (!is_int($name) && !preg_match('/^:/', $name)) { | |
$newName = ":$name"; | |
unset($bind[$name]); | |
$bind[$newName] = $value; | |
} | |
} | |
} | |
try { | |
return parent::query($sql, $bind); | |
} catch (PDOException $e) { | |
/** | |
* @see Zend_Db_Statement_Exception | |
*/ | |
require_once 'Zend/Db/Statement/Exception.php'; | |
throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); | |
} | |
} | |
/** | |
* Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. | |
* | |
* As a convention, on RDBMS brands that support sequences | |
* (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence | |
* from the arguments and returns the last id generated by that sequence. | |
* On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method | |
* returns the last value generated for such a column, and the table name | |
* argument is disregarded. | |
* | |
* The IDENTITY_VAL_LOCAL() function gives the last generated identity value | |
* in the current process, even if it was for a GENERATED column. | |
* | |
* @param string $tableName OPTIONAL | |
* @param string $primaryKey OPTIONAL | |
* @param string $idType OPTIONAL used for i5 platform to define sequence/idenity unique value | |
* @return string | |
*/ | |
public function lastInsertId($tableName = null, $primaryKey = null, $idType = null) | |
{ | |
$this->_connect(); | |
if ($tableName !== null) { | |
$sequenceName = $tableName; | |
if ($primaryKey) { | |
$sequenceName .= "_$primaryKey"; | |
} | |
$sequenceName .= '_seq'; | |
return $this->lastSequenceId($sequenceName); | |
} | |
$sql = 'SELECT IDENTITY_VAL_LOCAL() AS VAL FROM SYSIBM.SYSDUMMY1'; | |
$value = $this->fetchOne($sql); | |
return (string) $value; | |
} | |
/** | |
* Retrieve server version in PHP style | |
* Pdo_Idm doesn't support getAttribute(PDO::ATTR_SERVER_VERSION) | |
* | |
* @return null|string | |
*/ | |
public function getServerVersion() | |
{ | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment