%PDF- %PDF-
Direktori : /home/bitrix/www/bitrix/modules/main/lib/db/ |
Current File : //home/bitrix/www/bitrix/modules/main/lib/db/oracleconnection.php |
<?php namespace Bitrix\Main\DB; use Bitrix\Main\ArgumentException; use Bitrix\Main\ORM\Fields\ScalarField; /** * Class OracleConnection * * Class for Oracle database connections. * @package Bitrix\Main\DB */ class OracleConnection extends Connection { private $transaction = OCI_COMMIT_ON_SUCCESS; protected $lastInsertedId; /********************************************************** * SqlHelper **********************************************************/ /** * @return \Bitrix\Main\Db\SqlHelper */ protected function createSqlHelper() { return new OracleSqlHelper($this); } /*********************************************************** * Connection and disconnection ***********************************************************/ /** * Establishes a connection to the database. * Includes php_interface/after_connect_d7.php on success. * Throws exception on failure. * * @return void * @throws \Bitrix\Main\DB\ConnectionException */ protected function connectInternal() { if ($this->isConnected) return; if (($this->options & self::PERSISTENT) != 0) $connection = oci_pconnect($this->login, $this->password, $this->database); else $connection = oci_new_connect($this->login, $this->password, $this->database); if (!$connection) throw new ConnectionException('Oracle connect error', $this->getErrorMessage()); $this->isConnected = true; $this->resource = $connection; $this->afterConnected(); } /** * Disconnects from the database. * Does nothing if there was no connection established. * * @return void */ protected function disconnectInternal() { if (!$this->isConnected) return; $this->isConnected = false; oci_close($this->resource); } /********************************************************* * Query *********************************************************/ /** * Executes a query against connected database. * Rises SqlQueryException on any database error. * <p> * When object $trackerQuery passed then calls its startQuery and finishQuery * methods before and after query execution. * * @param string $sql Sql query. * @param array $binds Array of binds. * @param \Bitrix\Main\Diag\SqlTrackerQuery $trackerQuery Debug collector object. * * @return resource * @throws \Bitrix\Main\Db\SqlQueryException */ protected function queryInternal($sql, array $binds = null, \Bitrix\Main\Diag\SqlTrackerQuery $trackerQuery = null) { $this->connectInternal(); if ($trackerQuery != null) $trackerQuery->startQuery($sql, $binds); $result = oci_parse($this->resource, $sql); if (!$result) { if ($trackerQuery != null) $trackerQuery->finishQuery(); throw new SqlQueryException("", $this->getErrorMessage($this->resource), $sql); } $executionMode = $this->transaction; /** @var \OCI_Lob[] $clob */ $clob = array(); if (!empty($binds)) { $executionMode = OCI_DEFAULT; foreach ($binds as $key => $val) { $clob[$key] = oci_new_descriptor($this->resource, OCI_DTYPE_LOB); oci_bind_by_name($result, ":".$key, $clob[$key], -1, OCI_B_CLOB); } } if (!oci_execute($result, $executionMode)) { if ($trackerQuery != null) { $trackerQuery->finishQuery(); } throw new SqlQueryException("", $this->getErrorMessage($result), $sql); } if (!empty($binds)) { if (oci_num_rows($result) > 0) { foreach ($binds as $key => $val) { if($clob[$key]) { $clob[$key]->save($binds[$key]); } } } if ($this->transaction == OCI_COMMIT_ON_SUCCESS) { oci_commit($this->resource); } foreach ($binds as $key => $val) { if($clob[$key]) { $clob[$key]->free(); } } } if ($trackerQuery != null) { $trackerQuery->finishQuery(); } $this->lastQueryResult = $result; return $result; } /** * Returns database depended result of the query. * * @param resource $result Result of internal query function. * @param \Bitrix\Main\Diag\SqlTrackerQuery $trackerQuery Debug collector object. * * @return Result */ protected function createResult($result, \Bitrix\Main\Diag\SqlTrackerQuery $trackerQuery = null) { return new OracleResult($result, $this, $trackerQuery); } /** * Executes a query to the database. * * - query($sql) * - query($sql, $limit) * - query($sql, $offset, $limit) * - query($sql, $binds) * - query($sql, $binds, $limit) * - query($sql, $binds, $offset, $limit) * * @param string $sql Sql query. * @param array $binds Array of binds. * @param int $offset Offset of the first row to return, starting from 0. * @param int $limit Limit rows count. * * @return Result * @throws \Bitrix\Main\Db\SqlQueryException */ public function query($sql) { list($sql, $binds, $offset, $limit) = self::parseQueryFunctionArgs(func_get_args()); if (!empty($binds)) { $binds1 = $binds2 = ""; foreach ($binds as $key => $value) { if (strlen($value) > 0) { if ($binds1 != "") { $binds1 .= ","; $binds2 .= ","; } $binds1 .= $key; $binds2 .= ":".$key; } } if ($binds1 != "") $sql .= " RETURNING ".$binds1." INTO ".$binds2; } return parent::query($sql, $binds, $offset, $limit); } /** * Adds row to table and returns ID of the added row. * <p> * $identity parameter must be null when table does not have autoincrement column. * * @param string $tableName Name of the table for insertion of new row.. * @param array $data Array of columnName => Value pairs. * @param string $identity For Oracle only. * * @return integer * @throws \Bitrix\Main\Db\SqlQueryException */ public function add($tableName, array $data, $identity = "ID") { if($identity !== null && !isset($data[$identity])) $data[$identity] = $this->getNextId("sq_".$tableName); $insert = $this->getSqlHelper()->prepareInsert($tableName, $data); $binds = $insert[2]; $sql = "INSERT INTO ".$tableName."(".$insert[0].") ". "VALUES (".$insert[1].")"; $this->queryExecute($sql, $binds); $this->lastInsertedId = $data[$identity]; return $data[$identity]; } /** * Gets next value from the database sequence. * <p> * Sequence name may contain only A-Z,a-z,0-9 and _ characters. * * @param string $name Name of the sequence. * * @return null|string * @throws \Bitrix\Main\ArgumentNullException */ public function getNextId($name = "") { $name = preg_replace("/[^A-Za-z0-9_]+/i", "", $name); $name = trim($name); if($name == '') throw new \Bitrix\Main\ArgumentNullException("name"); $sql = "SELECT ".$this->getSqlHelper()->quote($name).".NEXTVAL FROM DUAL"; $result = $this->query($sql); if ($row = $result->fetch()) { return array_shift($row); } return null; } /** * @return integer */ public function getInsertedId() { return $this->lastInsertedId; } /** * Returns affected rows count from last executed query. * * @return integer */ public function getAffectedRowsCount() { return oci_num_rows($this->lastQueryResult); } /** * Checks if a table exists. * * @param string $tableName The table name. * * @return boolean */ public function isTableExists($tableName) { if (empty($tableName)) return false; $result = $this->queryScalar(" SELECT COUNT(TABLE_NAME) FROM USER_TABLES WHERE TABLE_NAME LIKE UPPER('".$this->getSqlHelper()->forSql($tableName)."') "); return ($result > 0); } /** * Checks if an index exists. * Actual columns in the index may differ from requested. * $columns may present an "prefix" of actual index columns. * * @param string $tableName A table name. * @param array $columns An array of columns in the index. * * @return boolean * @throws \Bitrix\Main\Db\SqlQueryException */ public function isIndexExists($tableName, array $columns) { return $this->getIndexName($tableName, $columns) !== null; } /** * Returns the name of an index. * * @param string $tableName A table name. * @param array $columns An array of columns in the index. * @param bool $strict The flag indicating that the columns in the index must exactly match the columns in the $arColumns parameter. * * @return string|null Name of the index or null if the index doesn't exist. * @throws \Bitrix\Main\Db\SqlQueryException */ public function getIndexName($tableName, array $columns, $strict = false) { if (!is_array($columns) || empty($columns)) return null; $isFunc = false; $indexes = array(); $result = $this->query("SELECT * FROM USER_IND_COLUMNS WHERE TABLE_NAME = upper('".$this->getSqlHelper()->forSql($tableName)."')"); while ($ar = $result->fetch()) { $indexes[$ar["INDEX_NAME"]][$ar["COLUMN_POSITION"] - 1] = $ar["COLUMN_NAME"]; if (strncmp($ar["COLUMN_NAME"], "SYS_NC", 6) === 0) { $isFunc = true; } } if ($isFunc) { $result = $this->query("SELECT * FROM USER_IND_EXPRESSIONS WHERE TABLE_NAME = upper('".$this->getSqlHelper()->forSql($tableName)."')"); while ($ar = $result->fetch()) { $indexes[$ar["INDEX_NAME"]][$ar["COLUMN_POSITION"] - 1] = $ar["COLUMN_EXPRESSION"]; } } $columnsList = implode(",", $columns); foreach ($indexes as $indexName => $indexColumns) { ksort($indexColumns); $indexColumnList = implode(",", $indexColumns); if ($strict) { if ($indexColumnList === $columnsList) return $indexName; } else { if (substr($indexColumnList, 0, strlen($columnsList)) === $columnsList) return $indexName; } } return null; } /** * Returns fields objects according to the columns of a table. * Table must exists. * * @param string $tableName The table name. * * @return ScalarField[] An array of objects with columns information. * @throws \Bitrix\Main\Db\SqlQueryException */ public function getTableFields($tableName) { if (!isset($this->tableColumnsCache[$tableName])) { $this->connectInternal(); $query = $this->queryInternal("SELECT * FROM ".$this->getSqlHelper()->quote($tableName)." WHERE ROWNUM = 0"); $result = $this->createResult($query); $this->tableColumnsCache[$tableName] = $result->getFields(); } return $this->tableColumnsCache[$tableName]; } /** * @param string $tableName Name of the new table. * @param ScalarField[] $fields Array with columns descriptions. * @param string[] $primary Array with primary key column names. * @param string[] $autoincrement Which columns will be auto incremented ones. * * @return void * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\Db\SqlQueryException */ public function createTable($tableName, $fields, $primary = array(), $autoincrement = array()) { $sql = 'CREATE TABLE '.$this->getSqlHelper()->quote($tableName).' ('; $sqlFields = array(); foreach ($fields as $columnName => $field) { if (!($field instanceof ScalarField)) { throw new ArgumentException(sprintf( 'Field `%s` should be an Entity\ScalarField instance', $columnName )); } $realColumnName = $field->getColumnName(); $sqlFields[] = $this->getSqlHelper()->quote($realColumnName) . ' ' . $this->getSqlHelper()->getColumnTypeByField($field) . ' ' . (in_array($columnName, $primary, true) ? 'NOT NULL' : 'NULL') ; } $sql .= join(', ', $sqlFields); if (!empty($primary)) { foreach ($primary as &$primaryColumn) { $realColumnName = $fields[$primaryColumn]->getColumnName(); $primaryColumn = $this->getSqlHelper()->quote($realColumnName); } $sql .= ', PRIMARY KEY('.join(', ', $primary).')'; } $sql .= ')'; $this->query($sql); // autoincrement field if (!empty($autoincrement)) { foreach ($autoincrement as $autoincrementColumn) { $autoincrementColumn = $fields[$autoincrementColumn]->getColumnName(); if ($autoincrementColumn == 'ID') { // old-school hack $aiName = $tableName; } else { $aiName = $tableName.'_'.$autoincrementColumn; } $this->query('CREATE SEQUENCE '.$this->getSqlHelper()->quote('sq_'.$aiName)); $this->query('CREATE OR REPLACE TRIGGER '.$this->getSqlHelper()->quote($aiName.'_insert').' BEFORE INSERT ON '.$this->getSqlHelper()->quote($tableName).' FOR EACH ROW BEGIN IF :NEW.'.$this->getSqlHelper()->quote($autoincrementColumn).' IS NULL THEN SELECT '.$this->getSqlHelper()->quote('sq_'.$aiName).'.NEXTVAL INTO :NEW.'.$this->getSqlHelper()->quote($autoincrementColumn).' FROM dual; END IF; END;' ); } } } /** * Renames the table. Renamed table must exists and new name must not be occupied by any database object. * * @param string $currentName Old name of the table. * @param string $newName New name of the table. * * @return void * @throws \Bitrix\Main\Db\SqlQueryException */ public function renameTable($currentName, $newName) { $this->query('RENAME '.$this->getSqlHelper()->quote($currentName).' TO '.$this->getSqlHelper()->quote($newName)); // handle auto increment: rename primary sequence for ID // properly we should check PRIMARY fields instead of ID: $aiName = $currentName.'_'.$fieldName, see createTable $aiName = $currentName; if ($this->queryScalar("SELECT 1 FROM user_sequences WHERE sequence_name=upper('".$this->getSqlHelper()->forSql('sq_'.$aiName)."')")) { // for fields excpet for ID here should be $newName.'_'.$fieldName, see createTable $newAiName = $newName; // rename sequence $this->query('RENAME '.$this->getSqlHelper()->quote('sq_'.$aiName).' TO '.$this->getSqlHelper()->quote('sq_'.$newAiName)); // recreate trigger $this->query('DROP TRIGGER '.$this->getSqlHelper()->quote($aiName.'_insert')); $this->query('CREATE OR REPLACE TRIGGER '.$this->getSqlHelper()->quote($newAiName.'_insert').' BEFORE INSERT ON '.$this->getSqlHelper()->quote($newName).' FOR EACH ROW BEGIN IF :NEW.'.$this->getSqlHelper()->quote('ID').' IS NULL THEN SELECT '.$this->getSqlHelper()->quote('sq_'.$newAiName).'.NEXTVAL INTO :NEW.'.$this->getSqlHelper()->quote('ID').' FROM dual; END IF; END;' ); } } /** * Drops the table. * * @param string $tableName Name of the table to be dropped. * * @return void * @throws \Bitrix\Main\Db\SqlQueryException */ public function dropTable($tableName) { $this->query('DROP TABLE '.$this->getSqlHelper()->quote($tableName).' CASCADE CONSTRAINTS'); // handle auto increment: delete primary sequence for ID // properly we should check PRIMARY fields instead of ID: $aiName = $currentName.'_'.$fieldName, see createTable $aiName = $tableName; if ($this->queryScalar("SELECT 1 FROM user_sequences WHERE sequence_name=upper('".$this->getSqlHelper()->forSql('sq_'.$aiName)."')")) { $this->query('DROP SEQUENCE '.$this->getSqlHelper()->quote('sq_'.$aiName)); } } /********************************************************* * Transaction *********************************************************/ /** * Starts new database transaction. * * @return void * @throws \Bitrix\Main\Db\SqlQueryException */ public function startTransaction() { $this->transaction = OCI_DEFAULT; } /** * Commits started database transaction. * * @return void * @throws \Bitrix\Main\Db\SqlQueryException */ public function commitTransaction() { $this->connectInternal(); OCICommit($this->resource); $this->transaction = OCI_COMMIT_ON_SUCCESS; } /** * Rollbacks started database transaction. * * @return void * @throws \Bitrix\Main\Db\SqlQueryException */ public function rollbackTransaction() { $this->connectInternal(); OCIRollback($this->resource); $this->transaction = OCI_COMMIT_ON_SUCCESS; } /********************************************************* * Type, version, cache, etc. *********************************************************/ /** * Returns database type. * <ul> * <li> oracle * </ul> * * @return string * @see \Bitrix\Main\DB\Connection::getType */ public function getType() { return "oracle"; } /** * Returns connected database version. * Version presented in array of two elements. * - First (with index 0) is database version. * - Second (with index 1) is true when light/express version of database is used. * * @return array * @throws \Bitrix\Main\Db\SqlQueryException */ public function getVersion() { if ($this->version == null) { $version = $this->queryScalar('SELECT BANNER FROM v$version'); if ($version != null) { $version = trim($version); $this->versionExpress = (strpos($version, "Express Edition") > 0); preg_match("#[0-9]+\\.[0-9]+\\.[0-9]+#", $version, $arr); $this->version = $arr[0]; } } return array($this->version, $this->versionExpress); } /** * Returns error message of last failed database operation. * * @param resource $resource Connection or query result resource. * @return string */ protected function getErrorMessage($resource = null) { if ($resource) $error = oci_error($resource); else $error = oci_error(); if (!$error) return ""; $result = sprintf("[%s] %s", $error["code"], $error["message"]); if (!empty($error["sqltext"])) $result .= sprintf(" (%s)", $error["sqltext"]); return $result; } }