phplibrary/creole/drivers/oracle/OCI8PreparedStatement.php
2016-06-29 18:51:32 +03:00

424 lines
15 KiB
PHP

<?php
/*
* $Id: OCI8PreparedStatement.php,v 1.26 2006/01/30 21:32:05 sethr Exp $
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information please see
* <http://creole.phpdb.org>.
*/
require_once 'creole/PreparedStatement.php';
require_once 'creole/common/PreparedStatementCommon.php';
/**
* Oracle (OCI8) implementation of PreparedStatement.
*
* @author David Giffin <david@giffin.org>
* @author Hans Lellelid <hans@xmpl.org>
* @version $Revision: 1.26 $
* @package creole.drivers.oracle
*/
class OCI8PreparedStatement extends PreparedStatementCommon implements PreparedStatement {
/**
* Descriptor holders for LOB values.
* There are other types of descriptors, but we need to keep
* them separate, because we need to execute the save()/savefile() method
* on lob descriptors.
* @var array object from oci_new_descriptor
*/
private $lobDescriptors = array();
/**
* Hold any Blob/Clob data.
* These can be matched (by key) to descriptors in $lobDescriptors.
* @var array Lob[]
*/
private $lobs = array();
/**
* Array to store the columns in an insert or update statement.
* This is necessary for the proper handling of lob variables
* @var arrary columns[]
*/
private $columns = array();
/**
* If the statement is set, free it.
* @see PreparedStatement::close()
*/
function close()
{
if (isset($this->stmt))
@oci_free_statement($this->stmt);
}
/**
* Nothing to do - since oci_bind is used to insert data, no escaping is needed
* @param string $str
* @return string
*/
protected function escape($str)
{
return $str;
}
/**
* Executes the SQL query in this PreparedStatement object and returns the resultset generated by the query.
* @param mixed $p1 Either (array) Parameters that will be set using PreparedStatement::set() before query is executed or (int) fetchmode.
* @param int $fetchmode The mode to use when fetching the results (e.g. ResultSet::FETCHMODE_NUM, ResultSet::FETCHMODE_ASSOC).
* @return ResultSet
* @throws SQLException if a database access error occurs.
*/
public function executeQuery($p1 = null, $fetchmode = null)
{
$params = null;
if ($fetchmode !== null) {
$params = $p1;
} elseif ($p1 !== null) {
if (is_array($p1)) $params = $p1;
else $fetchmode = $p1;
}
if ($params) {
for($i=0,$cnt=count($params); $i < $cnt; $i++) {
$this->set($i+1, $params[$i]);
}
}
$this->updateCount = null; // reset
$sql = $this->sqlToOracleBindVars($this->sql);
if ($this->limit > 0 || $this->offset > 0) {
$this->conn->applyLimit($sql, $this->offset, $this->limit);
}
$result = oci_parse($this->conn->getResource(), $sql);
if (!$result) {
throw new SQLException("Unable to prepare query", $this->conn->nativeError(), $this->sqlToOracleBindVars($this->sql));
}
// bind all variables
$this->bindVars($result);
$success = oci_execute($result, OCI_DEFAULT);
if (!$success) {
throw new SQLException("Unable to execute query", $this->conn->nativeError($result), $this->sqlToOracleBindVars($this->sql));
}
$this->resultSet = new OCI8ResultSet($this->conn, $result, $fetchmode);
return $this->resultSet;
}
/**
* Executes the SQL INSERT, UPDATE, or DELETE statement in this PreparedStatement object.
*
* @param array $params Parameters that will be set using PreparedStatement::set() before query is executed.
* @return int Number of affected rows (or 0 for drivers that return nothing).
* @throws SQLException if a database access error occurs.
*/
public function executeUpdate($params = null)
{
if ($params) {
for($i=0,$cnt=count($params); $i < $cnt; $i++) {
$this->set($i+1, $params[$i]);
}
}
if($this->resultSet) $this->resultSet->close();
$this->resultSet = null; // reset
$stmt = oci_parse($this->conn->getResource(), $this->sqlToOracleBindVars($this->sql));
if (!$stmt) {
throw new SQLException("Unable to prepare update", $this->conn->nativeError(), $this->sqlToOracleBindVars($this->sql));
}
// bind all variables
$this->bindVars($stmt);
// Even if autocommit is on, delay commit until after LOBS have been saved
$success = oci_execute($stmt, OCI_DEFAULT);
if (!$success) {
throw new SQLException("Unable to execute update", $this->conn->nativeError($stmt), $this->sqlToOracleBindVars($this->sql));
}
// save data in any LOB descriptors, then free them
foreach($this->lobDescriptors as $paramIndex => $lobster) {
$lob = $this->lobs[$paramIndex]; // corresponding Blob/Clob
if ($lob->isFromFile()) {
$success = $lobster->savefile($lob->getInputFile());
} else {
$success = $lobster->save($lob->getContents());
}
if (!$success) {
$lobster->free();
throw new SQLException("Error saving lob bound to " . $paramIndex);
}
$lobster->free();
}
if ($this->conn->getAutoCommit()) {
oci_commit($this->conn->getResource()); // perform deferred commit
}
$this->updateCount = @oci_num_rows($stmt);
return $this->updateCount;
}
/**
* Performs the actual binding of variables using oci_bind_by_name().
*
* This may seem like useless overhead, but the reason why calls to oci_bind_by_name()
* are not performed in the set*() methods is that it is possible that the SQL will
* need to be modified -- e.g. by a setLimit() call -- and re-prepared. We cannot assume
* that the statement has been prepared when the set*() calls are invoked. This also means,
* therefore, that the set*() calls will not throw exceptions; all exceptions will be thrown
* when the statement is prepared.
*
* @param resource $stmt The statement result of oci_parse to use for binding.
* @return void
*/
private function bindVars($stmt)
{
foreach ($this->boundInVars as $idx => $val) {
$idxName = ":var" . $idx;
if (!oci_bind_by_name($stmt, $idxName, $this->boundInVars[$idx], -1)) {
throw new SQLException("Erorr binding value to placeholder " . $idx);
}
} // foreach
foreach ($this->lobs as $idx => $val) {
$idxName = ":var" . $idx;
if (class_exists('Blob') && $val instanceof Blob){
if (!oci_bind_by_name($stmt, $idxName, $this->lobDescriptors[$idx], -1, OCI_B_BLOB))
throw new SQLException("Erorr binding blob to placeholder " . $idx);
} elseif (class_exists('Clob') && $val instanceof Clob){
if (!oci_bind_by_name($stmt, $idxName, $this->lobDescriptors[$idx], -1, OCI_B_CLOB))
throw new SQLException("Erorr binding clob to placeholder " . $idx);
}
} // foreach
}
/**
* Convert a Propel SQL into Oracle SQL
*
* Look for all of the '?' and replace with ":varX"
*
* @param string $sql SQL in Propel native format
* @return string SQL in Oracle Bind Var format
* @todo -cOCI8PreparedStatement Consider changing this implementation to use the fact that we
* already know where all the '?' chars are (in $positions array).
*/
private function sqlToOracleBindVars($sql)
{
$out = "";
$in_literal = 0;
$idxNum = 1;
for ($i = 0; $i < strlen($sql); $i++) {
$char = $sql[$i];
if (strcmp($char,"'")==0) {
$in_literal = ~$in_literal;
}
if (strcmp($char,"?")==0 && !$in_literal) {
if (array_key_exists($idxNum, $this->lobs)){
if (class_exists('Blob') && ($this->lobs[$idxNum] instanceof Blob))
$out .= "empty_blob()";
if (class_exists('Clob') && ($this->lobs[$idxNum] instanceof Clob))
$out .= "empty_clob()";
} else
$out .= ":var" . $idxNum;
$idxNum++;
} else {
$out .= $char;
}
}
if (isset($this->lobs) && !empty($this->lobs)) {
$this->setColumnArray();
$retstmt = " Returning ";
$collist = "";
$bindlist = "";
foreach ($this->lobs as $idx=>$val) {
$idxName = ":var" . $idx;
if ((class_exists('Blob') && $val instanceof Blob) || (class_exists('Clob') && $val instanceof Clob)) {
//the columns array starts at zero instead of 1 like the lobs array
$collist .= $this->columns[$idx-1] . ",";
$bindlist .= $idxName . ",";
}
}
if (!empty($collist))
$out .= $retstmt . rtrim($collist, ",") . " into " . rtrim($bindlist, ",");
}
return $out;
}
/**
* @param string $paramIndex
* @param mixed $blob Blob object or string containing data.
* @return void
*/
function setBlob($paramIndex, $blob)
{
require_once 'creole/util/Blob.php';
if (!($blob instanceof Blob)) {
$b = new Blob();
$b->setContents($blob);
$blob = $b;
}
$this->lobDescriptors[$paramIndex] = oci_new_descriptor($this->conn->getResource(), OCI_D_LOB);
$this->lobs[$paramIndex] = $blob;
}
/**
* @param string $paramIndex
* @param mixed $clob Clob object or string containing data.
* @return void
*/
function setClob($paramIndex, $clob)
{
require_once 'creole/util/Clob.php';
if (!($clob instanceof Clob)) {
$c = new Clob();
$c->setContents($clob);
$clob = $c;
}
$this->lobDescriptors[$paramIndex] = oci_new_descriptor($this->conn->getResource(), OCI_D_LOB);
$this->lobs[$paramIndex] = $clob;
}
/**
* Since bind variables in oracle have no special characters, this setString method differs from the
* common one in that it does not single quote strings.
*
* @param int $paramIndex
* @param string $value
* @return void
*/
function setString($paramIndex, $value)
{
if ($value === null) {
$this->setNull($paramIndex);
} else {
// it's ok to have a fatal error here, IMO, if object doesn't have
// __toString() and is being passed to this method.
if ( is_object ( $value ) ) {
$this->boundInVars[$paramIndex] = $value->__toString();
} else {
$this->boundInVars[$paramIndex] = (string)$value;
}
}
}
/**
* Copied this function from common/PreparedStatement.php and modified to work with Oracle
* Please note the format used with date() matches that of NLS_DATE_FORMAT set in
* OCI8Connection.php
*
* @param int $paramIndex
* @param string $value
* @return void
*/
function setTimestamp($paramIndex, $value)
{
if ($value === null) {
$this->setNull($paramIndex);
} else {
if (is_numeric($value)) $value = date('Y-m-d H:i:s', $value);
elseif (is_object($value)) $value = date('Y-m-d H:i:s', $value->getTime());
$this->boundInVars[$paramIndex] = $value;
}
}
/**
* Please note the format used with date() matches that of NLS_DATE_FORMAT set in
* OCI8Connection.php
*
* @param int $paramIndex
* @param string $value
* @return void
*/
function setDate($paramIndex, $value)
{
if ($value === null) {
$this->setNull($paramIndex);
} else {
if (is_numeric($value)) $value = date("Y-m-d", $value);
elseif (is_object($value)) $value = date("Y-m-d", $value->getTime());
$this->boundInVars[$paramIndex] = $value;
}
}
/**
* In order to send lob data (clob/blob) to the Oracle data base, the
* sqlToOracleBindVars function needs to have an ordered list of the
* columns being addressed in the sql statement.
* Since only insert and update statements require special handling,
* there are two ways to find the columns:
* 1) find the first set of () and parse out the columns names based on
* the token ','
* 2) find all the text strings to the left of the equal signs.
*
* @param void
* @return void
*/
private function setColumnArray()
{
$this->columns = array();
//handle the simple insert case first
if(strtoupper(substr($this->sql, 0, 6)) == 'INSERT') {
$firstPos = strpos($this->sql, '(');
$secPos = strpos($this->sql, ')');
$collist = substr($this->sql, $firstPos + 1, $secPos - $firstPos - 1);
$this->columns = explode(',', $collist);
}
if (strtoupper(substr($this->sql, 0, 6)) == 'UPDATE') {
//handle more complex update case
//first get the string setup so we can explode based on '=?'
//second split results from previous action based on ' '
// the last token from this should be a column name
$tmp = $this->sql;
$tmp = str_replace(" =", "=", $this->sql);
$tmp = str_replace("= ", "=", $tmp);
$tmp = str_replace(",", " ", $tmp);
$stage1 = explode("=?",$tmp);
foreach($stage1 as $chunk) {
$stage2 = explode(' ', $chunk);
$this->columns[count($this->columns)] = $stage2[count($stage2) - 1];
}
}
}
/**
* @param int $paramIndex
* @return void
*/
function setNull($paramIndex)
{
$this->boundInVars[$paramIndex] = '';
}
}