File indexing completed on 2025-01-19 05:21:02
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 * @see Zend_Db_Adapter_Abstract 0025 */ 0026 // require_once 'Zend/Db/Adapter/Abstract.php'; 0027 0028 /** 0029 * @see Zend_Db_Statement_Oracle 0030 */ 0031 // require_once 'Zend/Db/Statement/Oracle.php'; 0032 0033 /** 0034 * @category Zend 0035 * @package Zend_Db 0036 * @subpackage Adapter 0037 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0038 * @license http://framework.zend.com/license/new-bsd New BSD License 0039 */ 0040 class Zend_Db_Adapter_Oracle extends Zend_Db_Adapter_Abstract 0041 { 0042 /** 0043 * User-provided configuration. 0044 * 0045 * Basic keys are: 0046 * 0047 * username => (string) Connect to the database as this username. 0048 * password => (string) Password associated with the username. 0049 * dbname => Either the name of the local Oracle instance, or the 0050 * name of the entry in tnsnames.ora to which you want to connect. 0051 * persistent => (boolean) Set TRUE to use a persistent connection 0052 * @var array 0053 */ 0054 protected $_config = array( 0055 'dbname' => null, 0056 'username' => null, 0057 'password' => null, 0058 'persistent' => false 0059 ); 0060 0061 /** 0062 * Keys are UPPERCASE SQL datatypes or the constants 0063 * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE. 0064 * 0065 * Values are: 0066 * 0 = 32-bit integer 0067 * 1 = 64-bit integer 0068 * 2 = float or decimal 0069 * 0070 * @var array Associative array of datatypes to values 0, 1, or 2. 0071 */ 0072 protected $_numericDataTypes = array( 0073 Zend_Db::INT_TYPE => Zend_Db::INT_TYPE, 0074 Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, 0075 Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, 0076 'BINARY_DOUBLE' => Zend_Db::FLOAT_TYPE, 0077 'BINARY_FLOAT' => Zend_Db::FLOAT_TYPE, 0078 'NUMBER' => Zend_Db::FLOAT_TYPE, 0079 ); 0080 0081 /** 0082 * @var integer 0083 */ 0084 protected $_execute_mode = null; 0085 0086 /** 0087 * Default class name for a DB statement. 0088 * 0089 * @var string 0090 */ 0091 protected $_defaultStmtClass = 'Zend_Db_Statement_Oracle'; 0092 0093 /** 0094 * Check if LOB field are returned as string 0095 * instead of OCI-Lob object 0096 * 0097 * @var boolean 0098 */ 0099 protected $_lobAsString = null; 0100 0101 /** 0102 * Creates a connection resource. 0103 * 0104 * @return void 0105 * @throws Zend_Db_Adapter_Oracle_Exception 0106 */ 0107 protected function _connect() 0108 { 0109 if (is_resource($this->_connection)) { 0110 // connection already exists 0111 return; 0112 } 0113 0114 if (!extension_loaded('oci8')) { 0115 /** 0116 * @see Zend_Db_Adapter_Oracle_Exception 0117 */ 0118 // require_once 'Zend/Db/Adapter/Oracle/Exception.php'; 0119 throw new Zend_Db_Adapter_Oracle_Exception('The OCI8 extension is required for this adapter but the extension is not loaded'); 0120 } 0121 0122 $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS); 0123 0124 $connectionFuncName = ($this->_config['persistent'] == true) ? 'oci_pconnect' : 'oci_connect'; 0125 0126 $this->_connection = @$connectionFuncName( 0127 $this->_config['username'], 0128 $this->_config['password'], 0129 $this->_config['dbname'], 0130 $this->_config['charset']); 0131 0132 // check the connection 0133 if (!$this->_connection) { 0134 /** 0135 * @see Zend_Db_Adapter_Oracle_Exception 0136 */ 0137 // require_once 'Zend/Db/Adapter/Oracle/Exception.php'; 0138 throw new Zend_Db_Adapter_Oracle_Exception(oci_error()); 0139 } 0140 } 0141 0142 /** 0143 * Test if a connection is active 0144 * 0145 * @return boolean 0146 */ 0147 public function isConnected() 0148 { 0149 return ((bool) (is_resource($this->_connection) 0150 && (get_resource_type($this->_connection) == 'oci8 connection' 0151 || get_resource_type($this->_connection) == 'oci8 persistent connection'))); 0152 } 0153 0154 /** 0155 * Force the connection to close. 0156 * 0157 * @return void 0158 */ 0159 public function closeConnection() 0160 { 0161 if ($this->isConnected()) { 0162 oci_close($this->_connection); 0163 } 0164 $this->_connection = null; 0165 } 0166 0167 /** 0168 * Activate/deactivate return of LOB as string 0169 * 0170 * @param string $lob_as_string 0171 * @return Zend_Db_Adapter_Oracle 0172 */ 0173 public function setLobAsString($lobAsString) 0174 { 0175 $this->_lobAsString = (bool) $lobAsString; 0176 return $this; 0177 } 0178 0179 /** 0180 * Return whether or not LOB are returned as string 0181 * 0182 * @return boolean 0183 */ 0184 public function getLobAsString() 0185 { 0186 if ($this->_lobAsString === null) { 0187 // if never set by user, we use driver option if it exists otherwise false 0188 if (isset($this->_config['driver_options']) && 0189 isset($this->_config['driver_options']['lob_as_string'])) { 0190 $this->_lobAsString = (bool) $this->_config['driver_options']['lob_as_string']; 0191 } else { 0192 $this->_lobAsString = false; 0193 } 0194 } 0195 return $this->_lobAsString; 0196 } 0197 0198 /** 0199 * Returns an SQL statement for preparation. 0200 * 0201 * @param string $sql The SQL statement with placeholders. 0202 * @return Zend_Db_Statement_Oracle 0203 */ 0204 public function prepare($sql) 0205 { 0206 $this->_connect(); 0207 $stmtClass = $this->_defaultStmtClass; 0208 if (!class_exists($stmtClass)) { 0209 // require_once 'Zend/Loader.php'; 0210 Zend_Loader::loadClass($stmtClass); 0211 } 0212 $stmt = new $stmtClass($this, $sql); 0213 if ($stmt instanceof Zend_Db_Statement_Oracle) { 0214 $stmt->setLobAsString($this->getLobAsString()); 0215 } 0216 $stmt->setFetchMode($this->_fetchMode); 0217 return $stmt; 0218 } 0219 0220 /** 0221 * Quote a raw string. 0222 * 0223 * @param string $value Raw string 0224 * @return string Quoted string 0225 */ 0226 protected function _quote($value) 0227 { 0228 if (is_int($value) || is_float($value)) { 0229 return $value; 0230 } 0231 $value = str_replace("'", "''", $value); 0232 return "'" . addcslashes($value, "\000\n\r\\\032") . "'"; 0233 } 0234 0235 /** 0236 * Quote a table identifier and alias. 0237 * 0238 * @param string|array|Zend_Db_Expr $ident The identifier or expression. 0239 * @param string $alias An alias for the table. 0240 * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option. 0241 * @return string The quoted identifier and alias. 0242 */ 0243 public function quoteTableAs($ident, $alias = null, $auto = false) 0244 { 0245 // Oracle doesn't allow the 'AS' keyword between the table identifier/expression and alias. 0246 return $this->_quoteIdentifierAs($ident, $alias, $auto, ' '); 0247 } 0248 0249 /** 0250 * Return the most recent value from the specified sequence in the database. 0251 * This is supported only on RDBMS brands that support sequences 0252 * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. 0253 * 0254 * @param string $sequenceName 0255 * @return string 0256 */ 0257 public function lastSequenceId($sequenceName) 0258 { 0259 $this->_connect(); 0260 $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual'; 0261 $value = $this->fetchOne($sql); 0262 return $value; 0263 } 0264 0265 /** 0266 * Generate a new value from the specified sequence in the database, and return it. 0267 * This is supported only on RDBMS brands that support sequences 0268 * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. 0269 * 0270 * @param string $sequenceName 0271 * @return string 0272 */ 0273 public function nextSequenceId($sequenceName) 0274 { 0275 $this->_connect(); 0276 $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual'; 0277 $value = $this->fetchOne($sql); 0278 return $value; 0279 } 0280 0281 /** 0282 * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. 0283 * 0284 * As a convention, on RDBMS brands that support sequences 0285 * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence 0286 * from the arguments and returns the last id generated by that sequence. 0287 * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method 0288 * returns the last value generated for such a column, and the table name 0289 * argument is disregarded. 0290 * 0291 * Oracle does not support IDENTITY columns, so if the sequence is not 0292 * specified, this method returns null. 0293 * 0294 * @param string $tableName OPTIONAL Name of table. 0295 * @param string $primaryKey OPTIONAL Name of primary key column. 0296 * @return string 0297 */ 0298 public function lastInsertId($tableName = null, $primaryKey = null) 0299 { 0300 if ($tableName !== null) { 0301 $sequenceName = $tableName; 0302 if ($primaryKey) { 0303 $sequenceName .= "_$primaryKey"; 0304 } 0305 $sequenceName .= '_seq'; 0306 return $this->lastSequenceId($sequenceName); 0307 } 0308 0309 // No support for IDENTITY columns; return null 0310 return null; 0311 } 0312 0313 /** 0314 * Returns a list of the tables in the database. 0315 * 0316 * @return array 0317 */ 0318 public function listTables() 0319 { 0320 $this->_connect(); 0321 $data = $this->fetchCol('SELECT table_name FROM all_tables'); 0322 return $data; 0323 } 0324 0325 /** 0326 * Returns the column descriptions for a table. 0327 * 0328 * The return value is an associative array keyed by the column name, 0329 * as returned by the RDBMS. 0330 * 0331 * The value of each array element is an associative array 0332 * with the following keys: 0333 * 0334 * SCHEMA_NAME => string; name of schema 0335 * TABLE_NAME => string; 0336 * COLUMN_NAME => string; column name 0337 * COLUMN_POSITION => number; ordinal position of column in table 0338 * DATA_TYPE => string; SQL datatype name of column 0339 * DEFAULT => string; default expression of column, null if none 0340 * NULLABLE => boolean; true if column can have nulls 0341 * LENGTH => number; length of CHAR/VARCHAR 0342 * SCALE => number; scale of NUMERIC/DECIMAL 0343 * PRECISION => number; precision of NUMERIC/DECIMAL 0344 * UNSIGNED => boolean; unsigned property of an integer type 0345 * PRIMARY => boolean; true if column is part of the primary key 0346 * PRIMARY_POSITION => integer; position of column in primary key 0347 * IDENTITY => integer; true if column is auto-generated with unique values 0348 * 0349 * @todo Discover integer unsigned property. 0350 * 0351 * @param string $tableName 0352 * @param string $schemaName OPTIONAL 0353 * @return array 0354 */ 0355 public function describeTable($tableName, $schemaName = null) 0356 { 0357 $version = $this->getServerVersion(); 0358 if (($version === null) || version_compare($version, '9.0.0', '>=')) { 0359 $sql = "SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE, 0360 TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH, 0361 TC.DATA_SCALE, TC.DATA_PRECISION, C.CONSTRAINT_TYPE, CC.POSITION 0362 FROM ALL_TAB_COLUMNS TC 0363 LEFT JOIN (ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C 0364 ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND CC.TABLE_NAME = C.TABLE_NAME AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P')) 0365 ON TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME 0366 WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)"; 0367 $bind[':TBNAME'] = $tableName; 0368 if ($schemaName) { 0369 $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)'; 0370 $bind[':SCNAME'] = $schemaName; 0371 } 0372 $sql .= ' ORDER BY TC.COLUMN_ID'; 0373 } else { 0374 $subSql="SELECT AC.OWNER, AC.TABLE_NAME, ACC.COLUMN_NAME, AC.CONSTRAINT_TYPE, ACC.POSITION 0375 from ALL_CONSTRAINTS AC, ALL_CONS_COLUMNS ACC 0376 WHERE ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME 0377 AND ACC.TABLE_NAME = AC.TABLE_NAME 0378 AND ACC.OWNER = AC.OWNER 0379 AND AC.CONSTRAINT_TYPE = 'P' 0380 AND UPPER(AC.TABLE_NAME) = UPPER(:TBNAME)"; 0381 $bind[':TBNAME'] = $tableName; 0382 if ($schemaName) { 0383 $subSql .= ' AND UPPER(ACC.OWNER) = UPPER(:SCNAME)'; 0384 $bind[':SCNAME'] = $schemaName; 0385 } 0386 $sql="SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE, 0387 TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH, 0388 TC.DATA_SCALE, TC.DATA_PRECISION, CC.CONSTRAINT_TYPE, CC.POSITION 0389 FROM ALL_TAB_COLUMNS TC, ($subSql) CC 0390 WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME) 0391 AND TC.OWNER = CC.OWNER(+) AND TC.TABLE_NAME = CC.TABLE_NAME(+) AND TC.COLUMN_NAME = CC.COLUMN_NAME(+)"; 0392 if ($schemaName) { 0393 $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)'; 0394 } 0395 $sql .= ' ORDER BY TC.COLUMN_ID'; 0396 } 0397 0398 $stmt = $this->query($sql, $bind); 0399 0400 /** 0401 * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection 0402 */ 0403 $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); 0404 0405 $table_name = 0; 0406 $owner = 1; 0407 $column_name = 2; 0408 $data_type = 3; 0409 $data_default = 4; 0410 $nullable = 5; 0411 $column_id = 6; 0412 $data_length = 7; 0413 $data_scale = 8; 0414 $data_precision = 9; 0415 $constraint_type = 10; 0416 $position = 11; 0417 0418 $desc = array(); 0419 foreach ($result as $key => $row) { 0420 list ($primary, $primaryPosition, $identity) = array(false, null, false); 0421 if ($row[$constraint_type] == 'P') { 0422 $primary = true; 0423 $primaryPosition = $row[$position]; 0424 /** 0425 * Oracle does not support auto-increment keys. 0426 */ 0427 $identity = false; 0428 } 0429 $desc[$this->foldCase($row[$column_name])] = array( 0430 'SCHEMA_NAME' => $this->foldCase($row[$owner]), 0431 'TABLE_NAME' => $this->foldCase($row[$table_name]), 0432 'COLUMN_NAME' => $this->foldCase($row[$column_name]), 0433 'COLUMN_POSITION' => $row[$column_id], 0434 'DATA_TYPE' => $row[$data_type], 0435 'DEFAULT' => $row[$data_default], 0436 'NULLABLE' => (bool) ($row[$nullable] == 'Y'), 0437 'LENGTH' => $row[$data_length], 0438 'SCALE' => $row[$data_scale], 0439 'PRECISION' => $row[$data_precision], 0440 'UNSIGNED' => null, // @todo 0441 'PRIMARY' => $primary, 0442 'PRIMARY_POSITION' => $primaryPosition, 0443 'IDENTITY' => $identity 0444 ); 0445 } 0446 return $desc; 0447 } 0448 0449 /** 0450 * Leave autocommit mode and begin a transaction. 0451 * 0452 * @return void 0453 */ 0454 protected function _beginTransaction() 0455 { 0456 $this->_setExecuteMode(OCI_DEFAULT); 0457 } 0458 0459 /** 0460 * Commit a transaction and return to autocommit mode. 0461 * 0462 * @return void 0463 * @throws Zend_Db_Adapter_Oracle_Exception 0464 */ 0465 protected function _commit() 0466 { 0467 if (!oci_commit($this->_connection)) { 0468 /** 0469 * @see Zend_Db_Adapter_Oracle_Exception 0470 */ 0471 // require_once 'Zend/Db/Adapter/Oracle/Exception.php'; 0472 throw new Zend_Db_Adapter_Oracle_Exception(oci_error($this->_connection)); 0473 } 0474 $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS); 0475 } 0476 0477 /** 0478 * Roll back a transaction and return to autocommit mode. 0479 * 0480 * @return void 0481 * @throws Zend_Db_Adapter_Oracle_Exception 0482 */ 0483 protected function _rollBack() 0484 { 0485 if (!oci_rollback($this->_connection)) { 0486 /** 0487 * @see Zend_Db_Adapter_Oracle_Exception 0488 */ 0489 // require_once 'Zend/Db/Adapter/Oracle/Exception.php'; 0490 throw new Zend_Db_Adapter_Oracle_Exception(oci_error($this->_connection)); 0491 } 0492 $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS); 0493 } 0494 0495 /** 0496 * Set the fetch mode. 0497 * 0498 * @todo Support FETCH_CLASS and FETCH_INTO. 0499 * 0500 * @param integer $mode A fetch mode. 0501 * @return void 0502 * @throws Zend_Db_Adapter_Oracle_Exception 0503 */ 0504 public function setFetchMode($mode) 0505 { 0506 switch ($mode) { 0507 case Zend_Db::FETCH_NUM: // seq array 0508 case Zend_Db::FETCH_ASSOC: // assoc array 0509 case Zend_Db::FETCH_BOTH: // seq+assoc array 0510 case Zend_Db::FETCH_OBJ: // object 0511 $this->_fetchMode = $mode; 0512 break; 0513 case Zend_Db::FETCH_BOUND: // bound to PHP variable 0514 /** 0515 * @see Zend_Db_Adapter_Oracle_Exception 0516 */ 0517 // require_once 'Zend/Db/Adapter/Oracle/Exception.php'; 0518 throw new Zend_Db_Adapter_Oracle_Exception('FETCH_BOUND is not supported yet'); 0519 break; 0520 default: 0521 /** 0522 * @see Zend_Db_Adapter_Oracle_Exception 0523 */ 0524 // require_once 'Zend/Db/Adapter/Oracle/Exception.php'; 0525 throw new Zend_Db_Adapter_Oracle_Exception("Invalid fetch mode '$mode' specified"); 0526 break; 0527 } 0528 } 0529 0530 /** 0531 * Adds an adapter-specific LIMIT clause to the SELECT statement. 0532 * 0533 * @param string $sql 0534 * @param integer $count 0535 * @param integer $offset OPTIONAL 0536 * @return string 0537 * @throws Zend_Db_Adapter_Oracle_Exception 0538 */ 0539 public function limit($sql, $count, $offset = 0) 0540 { 0541 $count = intval($count); 0542 if ($count <= 0) { 0543 /** 0544 * @see Zend_Db_Adapter_Oracle_Exception 0545 */ 0546 // require_once 'Zend/Db/Adapter/Oracle/Exception.php'; 0547 throw new Zend_Db_Adapter_Oracle_Exception("LIMIT argument count=$count is not valid"); 0548 } 0549 0550 $offset = intval($offset); 0551 if ($offset < 0) { 0552 /** 0553 * @see Zend_Db_Adapter_Oracle_Exception 0554 */ 0555 // require_once 'Zend/Db/Adapter/Oracle/Exception.php'; 0556 throw new Zend_Db_Adapter_Oracle_Exception("LIMIT argument offset=$offset is not valid"); 0557 } 0558 0559 /** 0560 * Oracle does not implement the LIMIT clause as some RDBMS do. 0561 * We have to simulate it with subqueries and ROWNUM. 0562 * Unfortunately because we use the column wildcard "*", 0563 * this puts an extra column into the query result set. 0564 */ 0565 $limit_sql = "SELECT z2.* 0566 FROM ( 0567 SELECT z1.*, ROWNUM AS \"zend_db_rownum\" 0568 FROM ( 0569 " . $sql . " 0570 ) z1 0571 ) z2 0572 WHERE z2.\"zend_db_rownum\" BETWEEN " . ($offset+1) . " AND " . ($offset+$count); 0573 return $limit_sql; 0574 } 0575 0576 /** 0577 * @param integer $mode 0578 * @throws Zend_Db_Adapter_Oracle_Exception 0579 */ 0580 private function _setExecuteMode($mode) 0581 { 0582 switch($mode) { 0583 case OCI_COMMIT_ON_SUCCESS: 0584 case OCI_DEFAULT: 0585 case OCI_DESCRIBE_ONLY: 0586 $this->_execute_mode = $mode; 0587 break; 0588 default: 0589 /** 0590 * @see Zend_Db_Adapter_Oracle_Exception 0591 */ 0592 // require_once 'Zend/Db/Adapter/Oracle/Exception.php'; 0593 throw new Zend_Db_Adapter_Oracle_Exception("Invalid execution mode '$mode' specified"); 0594 break; 0595 } 0596 } 0597 0598 /** 0599 * @return int 0600 */ 0601 public function _getExecuteMode() 0602 { 0603 return $this->_execute_mode; 0604 } 0605 0606 /** 0607 * Check if the adapter supports real SQL parameters. 0608 * 0609 * @param string $type 'positional' or 'named' 0610 * @return bool 0611 */ 0612 public function supportsParameters($type) 0613 { 0614 switch ($type) { 0615 case 'named': 0616 return true; 0617 case 'positional': 0618 default: 0619 return false; 0620 } 0621 } 0622 0623 /** 0624 * Retrieve server version in PHP style 0625 * 0626 * @return string 0627 */ 0628 public function getServerVersion() 0629 { 0630 $this->_connect(); 0631 $version = oci_server_version($this->_connection); 0632 if ($version !== false) { 0633 $matches = null; 0634 if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) { 0635 return $matches[1]; 0636 } else { 0637 return null; 0638 } 0639 } else { 0640 return null; 0641 } 0642 } 0643 }