Files
portal-legacy/lib/symfony/vendor/creole/common/PreparedStatementCommon.php
Chris Sewell cf140a2e97 initial commit
2012-11-28 03:55:08 -05:00

641 lines
19 KiB
PHP
Executable File

<?php
/*
* $Id: PreparedStatementCommon.php,v 1.16 2005/11/13 01:30:00 gamr 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>.
*/
/**
* Class that represents a shared code for handling emulated pre-compiled statements.
*
* Many drivers do not take advantage of pre-compiling SQL statements; for these
* cases the precompilation is emulated. This emulation comes with slight penalty involved
* in parsing the queries, but provides other benefits such as a cleaner object model and ability
* to work with BLOB and CLOB values w/o needing special LOB-specific routines.
*
* @author Hans Lellelid <hans@xmpl.org>
* @version $Revision: 1.16 $
* @package creole.common
*/
abstract class PreparedStatementCommon {
/**
* The database connection.
* @var Connection
*/
protected $conn;
/**
* Max rows to retrieve from DB.
* @var int
*/
protected $limit = 0;
/**
* Offset at which to start processing DB rows.
* "Skip X rows"
* @var int
*/
protected $offset = 0;
/**
* The SQL this class operates on.
* @var string
*/
protected $sql;
/**
* Possibly contains a cached prepared SQL Statement.
* Gives an early out to replaceParams if the same
* query is run multiple times without changing the
* params.
* @var string
*/
protected $sql_cache;
/**
* Flag to set if the cache is upto date or not
* @var boolean
*/
protected $sql_cache_valid = false;
/**
* The string positions of the parameters in the SQL.
* @var array
*/
protected $positions;
/**
* Number of positions (simply to save processing).
* @var int
*/
protected $positionsCount;
/**
* Map of index => value for bound params.
* @var array string[]
*/
protected $boundInVars = array();
/**
* Temporarily hold a ResultSet object after an execute() query.
* @var ResultSet
*/
protected $resultSet;
/**
* Temporary hold the affected row cound after an execute() query.
* @var int
*/
protected $updateCount;
/**
* Create new prepared statement instance.
*
* @param object $conn Connection object
* @param string $sql The SQL to work with.
* @param array $positions The positions in SQL of ?'s.
* @param restult $stmt If the driver supports prepared queries, then $stmt will contain the statement to use.
*/
public function __construct(Connection $conn, $sql)
{
$this->conn = $conn;
$this->sql = $sql;
$this->positions = $this->parseQuery ( $sql );
// save processing later in cases where we may repeatedly exec statement
$this->positionsCount = count ( $this->positions );
}
/**
* Parse the SQL query for ? positions
*
* @param string $sql The query to process
* @return array Positions from the start of the string that ?'s appear at
*/
protected function parseQuery ( $sql )
{
$positions = array();
// match anything ? ' " or \ in $sql with an early out if we find nothing
if ( preg_match_all ( '([\?]|[\']|[\"]|[\\\])', $sql, $matches, PREG_OFFSET_CAPTURE ) !== 0 ) {
$matches = $matches['0'];
$open = NULL;
// go thru all our matches and see what we can find
for ( $i = 0, $j = count ( $matches ); $i < $j; $i++ ) {
switch ( $matches[$i]['0'] ) {
// if we already have an open " or ' then check if this is the end
// to close it or not
case $open:
$open = NULL;
break;
// we have a quote, set ourselves open
case '"':
case "'":
$open = $matches[$i]['0'];
break;
// check if it is an escaped quote and skip if it is
case '\\':
$next_match = $matches[$i+1]['0'];
if ( $next_match === '"' || $next_match === "'" ) {
$i++;
}
unset ( $next_match );
break;
// we found a ?, check we arent in an open "/' first and
// add it to the position list if we arent
default:
if ( $open === NULL ) {
$positions[] = $matches[$i]['1'];
}
}
unset ( $matches[$i] );
}
unset ( $open, $matches, $i, $j );
}
return $positions;
}
/**
* @see PreparedStatement::setLimit()
*/
public function setLimit($v)
{
$this->limit = (int) $v;
}
/**
* @see PreparedStatement::getLimit()
*/
public function getLimit()
{
return $this->limit;
}
/**
* @see PreparedStatement::setOffset()
*/
public function setOffset($v)
{
$this->offset = (int) $v;
}
/**
* @see PreparedStatement::getOffset()
*/
public function getOffset()
{
return $this->offset;
}
/**
* @see PreparedStatement::getResultSet()
*/
public function getResultSet()
{
return $this->resultSet;
}
/**
* @see PreparedStatement::getUpdateCount()
*/
public function getUpdateCount()
{
return $this->updateCount;
}
/**
* @see PreparedStatement::getMoreResults()
*/
public function getMoreResults()
{
if ($this->resultSet) $this->resultSet->close();
$this->resultSet = null;
return false;
}
/**
* @see PreparedStatement::getConnection()
*/
public function getConnection()
{
return $this->conn;
}
/**
* Statement resources do not exist for emulated prepared statements,
* so this just returns <code>null</code>.
* @return null
*/
public function getResource()
{
return null;
}
/**
* Nothing to close for emulated prepared statements.
*/
public function close()
{
}
/**
* Replaces placeholders with the specified parameter values in the SQL.
*
* This is for emulated prepared statements.
*
* @return string New SQL statement with parameters replaced.
* @throws SQLException - if param not bound.
*/
protected function replaceParams()
{
// early out if we still have the same query ready
if ( $this->sql_cache_valid === true ) {
return $this->sql_cache;
}
// Default behavior for this function is to behave in 'emulated' mode.
$sql = '';
$last_position = 0;
for ($position = 0; $position < $this->positionsCount; $position++) {
if (!isset($this->boundInVars[$position + 1])) {
throw new SQLException('Replace params: undefined query param: ' . ($position + 1));
}
$current_position = $this->positions[$position];
$sql .= substr($this->sql, $last_position, $current_position - $last_position);
$sql .= $this->boundInVars[$position + 1];
$last_position = $current_position + 1;
}
// append the rest of the query
$sql .= substr($this->sql, $last_position);
// just so we dont touch anything with a blob/clob
if ( strlen ( $sql ) > 2048 ) {
$this->sql_cache = $sql;
$this->sql_cache_valid = true;
return $this->sql_cache;
} else {
return $sql;
}
}
/**
* Executes the SQL query in this PreparedStatement object and returns the resultset generated by the query.
* We support two signatures for this method:
* - $stmt->executeQuery(ResultSet::FETCHMODE_NUM);
* - $stmt->executeQuery(array($param1, $param2), ResultSet::FETCHMODE_NUM);
* @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;
}
foreach ( (array) $params as $i=>$param ) {
$this->set ( $i + 1, $param );
unset ( $i, $param );
}
unset ( $params );
$this->updateCount = null; // reset
$sql = $this->replaceParams();
if ($this->limit > 0 || $this->offset > 0) {
$this->conn->applyLimit($sql, $this->offset, $this->limit);
}
$this->resultSet = $this->conn->executeQuery($sql, $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)
{
foreach ( (array) $params as $i=>$param ) {
$this->set ( $i + 1, $param );
unset ( $i, $param );
}
unset ( $params );
if($this->resultSet) $this->resultSet->close();
$this->resultSet = null; // reset
$sql = $this->replaceParams();
$this->updateCount = $this->conn->executeUpdate($sql);
return $this->updateCount;
}
/**
* Escapes special characters (usu. quotes) using native driver function.
* @param string $str The input string.
* @return string The escaped string.
*/
abstract protected function escape($str);
/**
* A generic set method.
*
* You can use this if you don't want to concern yourself with the details. It involves
* slightly more overhead than the specific settesr, since it grabs the PHP type to determine
* which method makes most sense.
*
* @param int $paramIndex
* @param mixed $value
* @return void
* @throws SQLException
*/
function set($paramIndex, $value)
{
$type = gettype($value);
if ($type == "object") {
if (is_a($value, 'Blob')) {
$this->setBlob($paramIndex, $value);
} elseif (is_a($value, 'Clob')) {
$this->setClob($paramIndex, $value);
} elseif (is_a($value, 'Date')) {
// can't be sure if the column type is a DATE, TIME, or TIMESTAMP column
// we'll just use TIMESTAMP by default; hopefully DB won't complain (if
// it does, then this method just shouldn't be used).
$this->setTimestamp($paramIndex, $value);
} else {
throw new SQLException("Unsupported object type passed to set(): " . get_class($value));
}
} else {
switch ( $type ) {
case 'integer':
$type = 'int';
break;
case 'double':
$type = 'float';
break;
}
$setter = 'set' . ucfirst($type); // PHP types are case-insensitive, but we'll do this in case that change
if ( method_exists ( $this, $setter ) ) {
$this->$setter($paramIndex, $value);
} else {
throw new SQLException ( "Unsupported datatype passed to set(): " . $type );
}
}
}
/**
* Sets an array.
* Unless a driver-specific method is used, this means simply serializing
* the passed parameter and storing it as a string.
* @param int $paramIndex
* @param array $value
* @return void
*/
function setArray($paramIndex, $value)
{
$this->sql_cache_valid = false;
if ($value === null) {
$this->setNull($paramIndex);
} else {
$this->boundInVars[$paramIndex] = "'" . $this->escape(serialize($value)) . "'";
}
}
/**
* Sets a boolean value.
* Default behavior is true = 1, false = 0.
* @param int $paramIndex
* @param boolean $value
* @return void
*/
function setBoolean($paramIndex, $value)
{
$this->sql_cache_valid = false;
if ($value === null) {
$this->setNull($paramIndex);
} else {
$this->boundInVars[$paramIndex] = (int) $value;
}
}
/**
* @see PreparedStatement::setBlob()
*/
function setBlob($paramIndex, $blob)
{
$this->sql_cache_valid = false;
if ($blob === null) {
$this->setNull($paramIndex);
} else {
// they took magic __toString() out of PHP5.0.0; this sucks
if (is_object($blob)) {
$this->boundInVars[$paramIndex] = "'" . $this->escape($blob->__toString()) . "'";
} else {
$this->boundInVars[$paramIndex] = "'" . $this->escape($blob) . "'";
}
}
}
/**
* @see PreparedStatement::setClob()
*/
function setClob($paramIndex, $clob)
{
$this->sql_cache_valid = false;
if ($clob === null) {
$this->setNull($paramIndex);
} else {
// they took magic __toString() out of PHP5.0.0; this sucks
if (is_object($clob)) {
$this->boundInVars[$paramIndex] = "'" . $this->escape($clob->__toString()) . "'";
} else {
$this->boundInVars[$paramIndex] = "'" . $this->escape($clob) . "'";
}
}
}
/**
* @param int $paramIndex
* @param string $value
* @return void
*/
function setDate($paramIndex, $value)
{
$this->sql_cache_valid = false;
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] = "'" . $this->escape($value) . "'";
}
}
/**
* @param int $paramIndex
* @param double $value
* @return void
*/
function setDecimal($paramIndex, $value)
{
$this->sql_cache_valid = false;
if ($value === null) {
$this->setNull($paramIndex);
} else {
$this->boundInVars[$paramIndex] = (float) $value;
}
}
/**
* @param int $paramIndex
* @param double $value
* @return void
*/
function setDouble($paramIndex, $value)
{
$this->sql_cache_valid = false;
if ($value === null) {
$this->setNull($paramIndex);
} else {
$this->boundInVars[$paramIndex] = (double) $value;
}
}
/**
* @param int $paramIndex
* @param float $value
* @return void
*/
function setFloat($paramIndex, $value)
{
$this->sql_cache_valid = false;
if ($value === null) {
$this->setNull($paramIndex);
} else {
$this->boundInVars[$paramIndex] = (float) $value;
}
}
/**
* @param int $paramIndex
* @param int $value
* @return void
*/
function setInt($paramIndex, $value)
{
$this->sql_cache_valid = false;
if ($value === null) {
$this->setNull($paramIndex);
} else {
$this->boundInVars[$paramIndex] = (int) $value;
}
}
/**
* Alias for setInt()
* @param int $paramIndex
* @param int $value
*/
function setInteger($paramIndex, $value)
{
$this->sql_cache_valid = false;
$this->setInt($paramIndex, $value);
}
/**
* @param int $paramIndex
* @return void
*/
function setNull($paramIndex)
{
$this->sql_cache_valid = false;
$this->boundInVars[$paramIndex] = 'NULL';
}
/**
* @param int $paramIndex
* @param string $value
* @return void
*/
function setString($paramIndex, $value)
{
$this->sql_cache_valid = false;
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] = "'" . $this->escape($value->__toString()) . "'";
} else {
$this->boundInVars[$paramIndex] = "'" . $this->escape((string)$value) . "'";
}
}
}
/**
* @param int $paramIndex
* @param string $value
* @return void
*/
function setTime($paramIndex, $value)
{
$this->sql_cache_valid = false;
if ($value === null) {
$this->setNull($paramIndex);
} else {
if ( is_numeric ( $value ) ) {
$value = date ('H:i:s', $value );
} elseif ( is_object ( $value ) ) {
$value = date ('H:i:s', $value->getTime ( ) );
}
$this->boundInVars [ $paramIndex ] = "'" . $this->escape ( $value ) . "'";
}
}
/**
* @param int $paramIndex
* @param string $value
* @return void
*/
function setTimestamp($paramIndex, $value)
{
$this->sql_cache_valid = false;
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] = "'".$this->escape($value)."'";
}
}
}