File indexing completed on 2025-01-26 05:24:56

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Db
0017  * @subpackage Adapter
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 
0024 /**
0025  * @see Zend_Db_Adapter_Pdo_Abstract
0026  */
0027 // require_once 'Zend/Db/Adapter/Pdo/Abstract.php';
0028 
0029 
0030 /**
0031  * Class for connecting to Oracle databases and performing common operations.
0032  *
0033  * @category   Zend
0034  * @package    Zend_Db
0035  * @subpackage Adapter
0036  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0037  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0038  */
0039 class Zend_Db_Adapter_Pdo_Oci extends Zend_Db_Adapter_Pdo_Abstract
0040 {
0041 
0042     /**
0043      * PDO type.
0044      *
0045      * @var string
0046      */
0047     protected $_pdoType = 'oci';
0048 
0049     /**
0050      * Default class name for a DB statement.
0051      *
0052      * @var string
0053      */
0054     protected $_defaultStmtClass = 'Zend_Db_Statement_Pdo_Oci';
0055 
0056     /**
0057      * Keys are UPPERCASE SQL datatypes or the constants
0058      * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
0059      *
0060      * Values are:
0061      * 0 = 32-bit integer
0062      * 1 = 64-bit integer
0063      * 2 = float or decimal
0064      *
0065      * @var array Associative array of datatypes to values 0, 1, or 2.
0066      */
0067     protected $_numericDataTypes = array(
0068         Zend_Db::INT_TYPE    => Zend_Db::INT_TYPE,
0069         Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
0070         Zend_Db::FLOAT_TYPE  => Zend_Db::FLOAT_TYPE,
0071         'BINARY_DOUBLE'      => Zend_Db::FLOAT_TYPE,
0072         'BINARY_FLOAT'       => Zend_Db::FLOAT_TYPE,
0073         'NUMBER'             => Zend_Db::FLOAT_TYPE
0074     );
0075 
0076     /**
0077      * Creates a PDO DSN for the adapter from $this->_config settings.
0078      *
0079      * @return string
0080      */
0081     protected function _dsn()
0082     {
0083         // baseline of DSN parts
0084         $dsn = $this->_config;
0085 
0086         if (isset($dsn['host'])) {
0087             $tns = 'dbname=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' .
0088                    '(HOST=' . $dsn['host'] . ')';
0089 
0090             if (isset($dsn['port'])) {
0091                 $tns .= '(PORT=' . $dsn['port'] . ')';
0092             } else {
0093                 $tns .= '(PORT=1521)';
0094             }
0095 
0096             $tns .= '))(CONNECT_DATA=(SID=' . $dsn['dbname'] . ')))';
0097         } else {
0098             $tns = 'dbname=' . $dsn['dbname'];
0099         }
0100 
0101         if (isset($dsn['charset'])) {
0102             $tns .= ';charset=' . $dsn['charset'];
0103         }
0104 
0105         return $this->_pdoType . ':' . $tns;
0106     }
0107 
0108     /**
0109      * Quote a raw string.
0110      * Most PDO drivers have an implementation for the quote() method,
0111      * but the Oracle OCI driver must use the same implementation as the
0112      * Zend_Db_Adapter_Abstract class.
0113      *
0114      * @param string $value     Raw string
0115      * @return string           Quoted string
0116      */
0117     protected function _quote($value)
0118     {
0119         if (is_int($value) || is_float($value)) {
0120             return $value;
0121         }
0122         $value = str_replace("'", "''", $value);
0123         return "'" . addcslashes($value, "\000\n\r\\\032") . "'";
0124     }
0125 
0126     /**
0127      * Quote a table identifier and alias.
0128      *
0129      * @param string|array|Zend_Db_Expr $ident The identifier or expression.
0130      * @param string $alias An alias for the table.
0131      * @return string The quoted identifier and alias.
0132      */
0133     public function quoteTableAs($ident, $alias = null, $auto = false)
0134     {
0135         // Oracle doesn't allow the 'AS' keyword between the table identifier/expression and alias.
0136         return $this->_quoteIdentifierAs($ident, $alias, $auto, ' ');
0137     }
0138 
0139     /**
0140      * Returns a list of the tables in the database.
0141      *
0142      * @return array
0143      */
0144     public function listTables()
0145     {
0146         $data = $this->fetchCol('SELECT table_name FROM all_tables');
0147         return $data;
0148     }
0149 
0150     /**
0151      * Returns the column descriptions for a table.
0152      *
0153      * The return value is an associative array keyed by the column name,
0154      * as returned by the RDBMS.
0155      *
0156      * The value of each array element is an associative array
0157      * with the following keys:
0158      *
0159      * SCHEMA_NAME      => string; name of schema
0160      * TABLE_NAME       => string;
0161      * COLUMN_NAME      => string; column name
0162      * COLUMN_POSITION  => number; ordinal position of column in table
0163      * DATA_TYPE        => string; SQL datatype name of column
0164      * DEFAULT          => string; default expression of column, null if none
0165      * NULLABLE         => boolean; true if column can have nulls
0166      * LENGTH           => number; length of CHAR/VARCHAR
0167      * SCALE            => number; scale of NUMERIC/DECIMAL
0168      * PRECISION        => number; precision of NUMERIC/DECIMAL
0169      * UNSIGNED         => boolean; unsigned property of an integer type
0170      * PRIMARY          => boolean; true if column is part of the primary key
0171      * PRIMARY_POSITION => integer; position of column in primary key
0172      * IDENTITY         => integer; true if column is auto-generated with unique values
0173      *
0174      * @todo Discover integer unsigned property.
0175      *
0176      * @param string $tableName
0177      * @param string $schemaName OPTIONAL
0178      * @return array
0179      */
0180     public function describeTable($tableName, $schemaName = null)
0181     {
0182         $version = $this->getServerVersion();
0183         if (($version === null) || version_compare($version, '9.0.0', '>=')) {
0184             $sql = "SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
0185                     TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
0186                     TC.DATA_SCALE, TC.DATA_PRECISION, C.CONSTRAINT_TYPE, CC.POSITION
0187                 FROM ALL_TAB_COLUMNS TC
0188                 LEFT JOIN (ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C
0189                     ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND CC.TABLE_NAME = C.TABLE_NAME AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P'))
0190                   ON TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME
0191                 WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)";
0192             $bind[':TBNAME'] = $tableName;
0193             if ($schemaName) {
0194                 $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
0195                 $bind[':SCNAME'] = $schemaName;
0196             }
0197             $sql .= ' ORDER BY TC.COLUMN_ID';
0198         } else {
0199             $subSql="SELECT AC.OWNER, AC.TABLE_NAME, ACC.COLUMN_NAME, AC.CONSTRAINT_TYPE, ACC.POSITION
0200                 from ALL_CONSTRAINTS AC, ALL_CONS_COLUMNS ACC
0201                   WHERE ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME
0202                     AND ACC.TABLE_NAME = AC.TABLE_NAME
0203                     AND ACC.OWNER = AC.OWNER
0204                     AND AC.CONSTRAINT_TYPE = 'P'
0205                     AND UPPER(AC.TABLE_NAME) = UPPER(:TBNAME)";
0206             $bind[':TBNAME'] = $tableName;
0207             if ($schemaName) {
0208                 $subSql .= ' AND UPPER(ACC.OWNER) = UPPER(:SCNAME)';
0209                 $bind[':SCNAME'] = $schemaName;
0210             }
0211             $sql="SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
0212                     TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
0213                     TC.DATA_SCALE, TC.DATA_PRECISION, CC.CONSTRAINT_TYPE, CC.POSITION
0214                 FROM ALL_TAB_COLUMNS TC, ($subSql) CC
0215                 WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)
0216                   AND TC.OWNER = CC.OWNER(+) AND TC.TABLE_NAME = CC.TABLE_NAME(+) AND TC.COLUMN_NAME = CC.COLUMN_NAME(+)";
0217             if ($schemaName) {
0218                 $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
0219             }
0220             $sql .= ' ORDER BY TC.COLUMN_ID';
0221         }
0222 
0223         $stmt = $this->query($sql, $bind);
0224 
0225         /**
0226          * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
0227          */
0228         $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
0229 
0230         $table_name      = 0;
0231         $owner           = 1;
0232         $column_name     = 2;
0233         $data_type       = 3;
0234         $data_default    = 4;
0235         $nullable        = 5;
0236         $column_id       = 6;
0237         $data_length     = 7;
0238         $data_scale      = 8;
0239         $data_precision  = 9;
0240         $constraint_type = 10;
0241         $position        = 11;
0242 
0243         $desc = array();
0244         foreach ($result as $key => $row) {
0245             list ($primary, $primaryPosition, $identity) = array(false, null, false);
0246             if ($row[$constraint_type] == 'P') {
0247                 $primary = true;
0248                 $primaryPosition = $row[$position];
0249                 /**
0250                  * Oracle does not support auto-increment keys.
0251                  */
0252                 $identity = false;
0253             }
0254             $desc[$this->foldCase($row[$column_name])] = array(
0255                 'SCHEMA_NAME'      => $this->foldCase($row[$owner]),
0256                 'TABLE_NAME'       => $this->foldCase($row[$table_name]),
0257                 'COLUMN_NAME'      => $this->foldCase($row[$column_name]),
0258                 'COLUMN_POSITION'  => $row[$column_id],
0259                 'DATA_TYPE'        => $row[$data_type],
0260                 'DEFAULT'          => $row[$data_default],
0261                 'NULLABLE'         => (bool) ($row[$nullable] == 'Y'),
0262                 'LENGTH'           => $row[$data_length],
0263                 'SCALE'            => $row[$data_scale],
0264                 'PRECISION'        => $row[$data_precision],
0265                 'UNSIGNED'         => null, // @todo
0266                 'PRIMARY'          => $primary,
0267                 'PRIMARY_POSITION' => $primaryPosition,
0268                 'IDENTITY'         => $identity
0269             );
0270         }
0271         return $desc;
0272     }
0273 
0274     /**
0275      * Return the most recent value from the specified sequence in the database.
0276      * This is supported only on RDBMS brands that support sequences
0277      * (e.g. Oracle, PostgreSQL, DB2).  Other RDBMS brands return null.
0278      *
0279      * @param string $sequenceName
0280      * @return integer
0281      */
0282     public function lastSequenceId($sequenceName)
0283     {
0284         $this->_connect();
0285         $value = $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual');
0286         return $value;
0287     }
0288 
0289     /**
0290      * Generate a new value from the specified sequence in the database, and return it.
0291      * This is supported only on RDBMS brands that support sequences
0292      * (e.g. Oracle, PostgreSQL, DB2).  Other RDBMS brands return null.
0293      *
0294      * @param string $sequenceName
0295      * @return integer
0296      */
0297     public function nextSequenceId($sequenceName)
0298     {
0299         $this->_connect();
0300         $value = $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual');
0301         return $value;
0302     }
0303 
0304     /**
0305      * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
0306      *
0307      * As a convention, on RDBMS brands that support sequences
0308      * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
0309      * from the arguments and returns the last id generated by that sequence.
0310      * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
0311      * returns the last value generated for such a column, and the table name
0312      * argument is disregarded.
0313      *
0314      * Oracle does not support IDENTITY columns, so if the sequence is not
0315      * specified, this method returns null.
0316      *
0317      * @param string $tableName   OPTIONAL Name of table.
0318      * @param string $primaryKey  OPTIONAL Name of primary key column.
0319      * @return string
0320      * @throws Zend_Db_Adapter_Oracle_Exception
0321      */
0322     public function lastInsertId($tableName = null, $primaryKey = null)
0323     {
0324         if ($tableName !== null) {
0325             $sequenceName = $tableName;
0326             if ($primaryKey) {
0327                 $sequenceName .= $this->foldCase("_$primaryKey");
0328             }
0329             $sequenceName .= $this->foldCase('_seq');
0330             return $this->lastSequenceId($sequenceName);
0331         }
0332         // No support for IDENTITY columns; return null
0333         return null;
0334     }
0335 
0336     /**
0337      * Adds an adapter-specific LIMIT clause to the SELECT statement.
0338      *
0339      * @param string $sql
0340      * @param integer $count
0341      * @param integer $offset
0342      * @throws Zend_Db_Adapter_Exception
0343      * @return string
0344      */
0345     public function limit($sql, $count, $offset = 0)
0346     {
0347         $count = intval($count);
0348         if ($count <= 0) {
0349             /** @see Zend_Db_Adapter_Exception */
0350             // require_once 'Zend/Db/Adapter/Exception.php';
0351             throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
0352         }
0353 
0354         $offset = intval($offset);
0355         if ($offset < 0) {
0356             /** @see Zend_Db_Adapter_Exception */
0357             // require_once 'Zend/Db/Adapter/Exception.php';
0358             throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
0359         }
0360 
0361         /**
0362          * Oracle does not implement the LIMIT clause as some RDBMS do.
0363          * We have to simulate it with subqueries and ROWNUM.
0364          * Unfortunately because we use the column wildcard "*",
0365          * this puts an extra column into the query result set.
0366          */
0367         $limit_sql = "SELECT z2.*
0368             FROM (
0369                 SELECT z1.*, ROWNUM AS \"zend_db_rownum\"
0370                 FROM (
0371                     " . $sql . "
0372                 ) z1
0373             ) z2
0374             WHERE z2.\"zend_db_rownum\" BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
0375         return $limit_sql;
0376     }
0377 
0378 }