initial commit

This commit is contained in:
Chris Sewell
2012-11-28 03:55:08 -05:00
parent 7adb399b2e
commit cf140a2e97
3247 changed files with 492437 additions and 0 deletions

View File

@ -0,0 +1,258 @@
<?php
/*
* $Id: ConnectionCommon.php,v 1.5 2005/10/17 19:03:51 dlawson_mi 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 contains some shared/default information for connections. Classes may wish to extend this so
* as not to worry about the sleep/wakeup methods, etc.
*
* In reality this class is not very useful yet, so there's not much incentive for drivers to extend this.
*
* @author Hans Lellelid <hans@xmpl.org>
* @version $Revision: 1.5 $
* @package creole.common
*/
abstract class ConnectionCommon {
// Constants that define transaction isolation levels.
// [We don't have any code using these yet, so there's no need
// to initialize these values at this point.]
// const TRANSACTION_NONE = 0;
// const TRANSACTION_READ_UNCOMMITTED = 1;
// const TRANSACTION_READ_COMMITTED = 2;
// const TRANSACTION_REPEATABLE_READ = 3;
// const TRANSACTION_SERIALIZABLE = 4;
/**
* The depth level of current transaction.
* @var int
*/
protected $transactionOpcount = 0;
/**
* DB connection resource id.
* @var resource
*/
protected $dblink;
/**
* Array hash of connection properties.
* @var array
*/
protected $dsn;
/**
* Flags (e.g. Connection::PERSISTENT) for current connection.
* @var int
*/
protected $flags = 0;
/**
* This "magic" method is invoked upon serialize() and works in tandem with the __wakeup()
* method to ensure that your database connection is serializable.
*
* This method returns an array containing the names of any members of your class
* which need to be serialized in order to allow the class to re-connect to the database
* when it is unserialized.
*
* <p>
* Developers:
*
* Note that you cannot serialize resources (connection links) and expect them to
* be valid when you unserialize. For this reason, you must re-connect to the database in the
* __wakeup() method.
*
* It's up to your class implimentation to ensure that the necessary data is serialized.
* You probably at least need to serialize:
*
* (1) the DSN array used by connect() method
* (2) Any flags that were passed to the connection
* (3) Possibly the autocommit state
*
* @return array The class variable names that should be serialized.
* @see __wakeup()
* @see DriverManager::getConnection()
* @see DatabaseInfo::__sleep()
*/
public function __sleep()
{
return array('dsn', 'flags');
}
/**
* This "magic" method is invoked upon unserialize().
* This method will re-connects to the database using the information that was
* stored using the __sleep() method.
* @see __sleep()
*/
public function __wakeup()
{
$this->connect($this->dsn, $this->flags);
}
/**
* @see Connection::getResource()
*/
public function getResource()
{
return $this->dblink;
}
/**
* @see Connection::getDSN()
*/
public function getDSN() {
return $this->dsn;
}
/**
* @see Connection::getFlags()
*/
public function getFlags()
{
return $this->flags;
}
/**
* Creates a CallableStatement object for calling database stored procedures.
*
* @param string $sql
* @return CallableStatement
*/
public function prepareCall($sql)
{
throw new SQLException("Current driver does not support stored procedures using CallableStatement.");
}
/**
* Driver classes should override this if they support transactions.
*
* @return boolean
*/
public function supportsNestedTrans()
{
return false;
}
/**
* Begins a transaction (if supported).
*/
public function begin()
{
if ($this->transactionOpcount === 0 || $this->supportsNestedTrans()) {
$this->beginTrans();
}
$this->transactionOpcount++;
}
/**
* Commits statements in a transaction.
*/
public function commit()
{
if ($this->transactionOpcount > 0) {
if ($this->transactionOpcount == 1 || $this->supportsNestedTrans()) {
$this->commitTrans();
}
$this->transactionOpcount--;
}
}
/**
* Rollback changes in a transaction.
*/
public function rollback()
{
if ($this->transactionOpcount > 0) {
if ($this->transactionOpcount == 1 || $this->supportsNestedTrans()) {
$this->rollbackTrans();
}
$this->transactionOpcount--;
}
}
/**
* Enable/disable automatic commits.
*
* Pushes SQLWarning onto $warnings stack if the autocommit value is being changed mid-transaction. This function
* is overridden by driver classes so that they can perform the necessary begin/end transaction SQL.
*
* If auto-commit is being set to TRUE, then the current transaction will be committed immediately.
*
* @param boolean $bit New value for auto commit.
* @return void
*/
public function setAutoCommit($bit)
{
if ($this->transactionOpcount > 0) {
trigger_error("Changing autocommit in mid-transaction; committing " . $this->transactionOpcount . " uncommitted statements.", E_USER_WARNING);
}
if (!$bit) {
$this->begin();
}
else {
$this->commit();
}
}
/**
* Get auto-commit status.
*
* @return boolean
*/
public function getAutoCommit()
{
return ($this->transactionOpcount == 0);
}
/**
* Begin new transaction.
* Driver classes should override this method if they support transactions.
*/
protected function beginTrans()
{
}
/**
* Commit the current transaction.
* Driver classes should override this method if they support transactions.
*/
protected function commitTrans()
{
}
/**
* Roll back (undo) the current transaction.
* Driver classes should override this method if they support transactions.
*/
protected function rollbackTrans()
{
}
/**
* Returns false if connection is closed.
* @return boolean
*/
public function isConnected()
{
return !empty($this->dblink);
}
}

View File

@ -0,0 +1,640 @@
<?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)."'";
}
}
}

View File

@ -0,0 +1,447 @@
<?php
/*
* $Id: ResultSetCommon.php,v 1.9 2006/01/17 19:44:38 hlellelid 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>.
*/
/**
* This class implements many shared or common methods needed by resultset drivers.
*
* This class may (optionally) be extended by driver classes simply to make it easier
* to create driver classes. This is also useful in the early stages of Creole development
* as it means that API changes affect fewer files. As Creole matures/stabalizes having
* a common class may become less useful, as drivers may have their own ways of doing things
* (and we'll have a solid unit test framework to make sure drivers conform to the API
* described by the interfaces).
*
* The get*() methods in this class will format values before returning them. Note
* that if they will return <code>null</code> if the database returned <code>NULL</code>
* which makes these functions easier to use than simply typecasting the values from the
* db. If the requested column does not exist than an exception (SQLException) will be thrown.
*
* <code>
* $rs = $conn->executeQuery("SELECT MAX(stamp) FROM event", ResultSet::FETCHMODE_NUM);
* $rs->next();
*
* $max_stamp = $rs->getTimestamp(1, "d/m/Y H:i:s");
* // $max_stamp will be date string or null if no MAX(stamp) was found
*
* $max_stamp = $rs->getTimestamp("max(stamp)", "d/m/Y H:i:s");
* // will THROW EXCEPTION, because the resultset was fetched using numeric indexing
* // SQLException: Invalid resultset column: max(stamp)
* </code>
*
* @author Hans Lellelid <hans@xmpl.org>
* @version $Revision: 1.9 $
* @package creole.common
*/
abstract class ResultSetCommon {
/**
* The fetchmode for this recordset.
* @var int
*/
protected $fetchmode;
/**
* DB connection.
* @var Connection
*/
protected $conn;
/**
* Resource identifier used for native result set handling.
* @var resource
*/
protected $result;
/**
* The current cursor position (row number). First row is 1. Before first row is 0.
* @var int
*/
protected $cursorPos = 0;
/**
* The current unprocessed record/row from the db.
* @var array
*/
protected $fields;
/**
* Whether to convert assoc col case.
* @var boolean
*/
protected $lowerAssocCase = false;
/**
* Whether to apply rtrim() to strings.
* @var boolean
*/
protected $rtrimString = false;
/**
* Constructor.
*/
public function __construct(Connection $conn, $result, $fetchmode = null)
{
$this->conn = $conn;
$this->result = $result;
if ($fetchmode !== null) {
$this->fetchmode = $fetchmode;
} else {
$this->fetchmode = ResultSet::FETCHMODE_ASSOC; // default
}
$this->lowerAssocCase = (($conn->getFlags() & Creole::COMPAT_ASSOC_LOWER) === Creole::COMPAT_ASSOC_LOWER);
$this->rtrimString = (($conn->getFlags() & Creole::COMPAT_RTRIM_STRING) === Creole::COMPAT_RTRIM_STRING);
}
/**
* Destructor
*
* Free db result resource.
*/
public function __destruct()
{
$this->close();
}
/**
* @see ResultSet::getIterator()
*/
public function getIterator()
{
require_once 'creole/ResultSetIterator.php';
return new ResultSetIterator($this);
}
/**
* @see ResultSet::getResource()
*/
public function getResource()
{
return $this->result;
}
/**
* @see ResultSet::isLowereAssocCase()
*/
public function isLowerAssocCase()
{
return $this->lowerAssocCase;
}
/**
* @see ResultSet::setFetchmode()
*/
public function setFetchmode($mode)
{
$this->fetchmode = $mode;
}
/**
* @see ResultSet::getFetchmode()
*/
public function getFetchmode()
{
return $this->fetchmode;
}
/**
* @see ResultSet::previous()
*/
public function previous()
{
// Go back 2 spaces so that we can then advance 1 space.
$ok = $this->seek($this->cursorPos - 2);
if ($ok === false) {
$this->beforeFirst();
return false;
}
return $this->next();
}
/**
* @see ResultSet::isBeforeFirst()
*/
public function relative($offset)
{
// which absolute row number are we seeking
$pos = $this->cursorPos + ($offset - 1);
$ok = $this->seek($pos);
if ($ok === false) {
if ($pos < 0) {
$this->beforeFirst();
} else {
$this->afterLast();
}
} else {
$ok = $this->next();
}
return $ok;
}
/**
* @see ResultSet::absolute()
*/
public function absolute($pos)
{
$ok = $this->seek( $pos - 1 ); // compensate for next() factor
if ($ok === false) {
if ($pos - 1 < 0) {
$this->beforeFirst();
} else {
$this->afterLast();
}
} else {
$ok = $this->next();
}
return $ok;
}
/**
* @see ResultSet::first()
*/
public function first()
{
if($this->cursorPos !== 0) { $this->seek(0); }
return $this->next();
}
/**
* @see ResultSet::last()
*/
public function last()
{
if($this->cursorPos !== ($last = $this->getRecordCount() - 1)) {
$this->seek( $last );
}
return $this->next();
}
/**
* @see ResultSet::beforeFirst()
*/
public function beforeFirst()
{
$this->cursorPos = 0;
}
/**
* @see ResultSet::afterLast()
*/
public function afterLast()
{
$this->cursorPos = $this->getRecordCount() + 1;
}
/**
* @see ResultSet::isAfterLast()
*/
public function isAfterLast()
{
return ($this->cursorPos === $this->getRecordCount() + 1);
}
/**
* @see ResultSet::isBeforeFirst()
*/
public function isBeforeFirst()
{
return ($this->cursorPos === 0);
}
/**
* @see ResultSet::getCursorPos()
*/
public function getCursorPos()
{
return $this->cursorPos;
}
/**
* @see ResultSet::getRow()
*/
public function getRow()
{
return $this->fields;
}
/**
* @see ResultSet::get()
*/
public function get($column)
{
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
return $this->fields[$idx];
}
/**
* @see ResultSet::getArray()
*/
public function getArray($column)
{
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
if ($this->fields[$idx] === null) { return null; }
return (array) unserialize($this->fields[$idx]);
}
/**
* @see ResultSet::getBoolean()
*/
public function getBoolean($column)
{
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
if ($this->fields[$idx] === null) { return null; }
return (boolean) $this->fields[$idx];
}
/**
* @see ResultSet::getBlob()
*/
public function getBlob($column)
{
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
if ($this->fields[$idx] === null) { return null; }
require_once 'creole/util/Blob.php';
$b = new Blob();
$b->setContents($this->fields[$idx]);
return $b;
}
/**
* @see ResultSet::getClob()
*/
public function getClob($column)
{
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
if ($this->fields[$idx] === null) { return null; }
require_once 'creole/util/Clob.php';
$c = new Clob();
$c->setContents($this->fields[$idx]);
return $c;
}
/**
* @see ResultSet::getDate()
*/
public function getDate($column, $format = '%x')
{
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
if ($this->fields[$idx] === null) { return null; }
$ts = strtotime($this->fields[$idx]);
if ($ts === -1 || $ts === false) { // in PHP 5.1 return value changes to FALSE
throw new SQLException("Unable to convert value at column " . $column . " to timestamp: " . $this->fields[$idx]);
}
if ($format === null) {
return $ts;
}
if (strpos($format, '%') !== false) {
return strftime($format, $ts);
} else {
return date($format, $ts);
}
}
/**
* @see ResultSet::getFloat()
*/
public function getFloat($column)
{
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
if ($this->fields[$idx] === null) { return null; }
return (float) $this->fields[$idx];
}
/**
* @see ResultSet::getInt()
*/
public function getInt($column)
{
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
if ($this->fields[$idx] === null) { return null; }
return (int) $this->fields[$idx];
}
/**
* @see ResultSet::getString()
*/
public function getString($column)
{
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
if ($this->fields[$idx] === null) { return null; }
return ($this->rtrimString ? rtrim($this->fields[$idx]) : (string) $this->fields[$idx]);
}
/**
* @see ResultSet::getTime()
*/
public function getTime($column, $format = '%X')
{
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
if ($this->fields[$idx] === null) { return null; }
$ts = strtotime($this->fields[$idx]);
if ($ts === -1 || $ts === false) { // in PHP 5.1 return value changes to FALSE
throw new SQLException("Unable to convert value at column " . (is_int($column) ? $column + 1 : $column) . " to timestamp: " . $this->fields[$idx]);
}
if ($format === null) {
return $ts;
}
if (strpos($format, '%') !== false) {
return strftime($format, $ts);
} else {
return date($format, $ts);
}
}
/**
* @see ResultSet::getTimestamp()
*/
public function getTimestamp($column, $format = 'Y-m-d H:i:s')
{
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
if ($this->fields[$idx] === null) { return null; }
$ts = strtotime($this->fields[$idx]);
if ($ts === -1 || $ts === false) { // in PHP 5.1 return value changes to FALSE
throw new SQLException("Unable to convert value at column " . $column . " to timestamp: " . $this->fields[$idx]);
}
if ($format === null) {
return $ts;
}
if (strpos($format, '%') !== false) {
return strftime($format, $ts);
} else {
return date($format, $ts);
}
}
}

View File

@ -0,0 +1,289 @@
<?php
/*
* $Id: StatementCommon.php,v 1.4 2004/06/13 02:31:07 hlellelid 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 contains common/shared functionality for Statements.
*
* @author Hans Lellelid <hans@xmpl.org>
* @version $Revision: 1.4 $
* @package creole.common
*/
abstract class StatementCommon {
/**
* The database connection.
* @var Connection
*/
protected $conn;
/**
* 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;
/**
* Array of warning objects generated by methods performed on result set.
* @var array SQLWarning[]
*/
protected $warnings = array();
/**
* The ResultSet class name.
* @var string
*/
protected $resultClass;
/**
* The prepared statement resource id.
* @var resource
*/
protected $stmt;
/**
* 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;
/**
* Create new statement instance.
*
* @param Connection $conn Connection object
*/
function __construct(Connection $conn)
{
$this->conn = $conn;
}
/**
* Sets the maximum number of rows to return from db.
* This will affect the SQL if the RDBMS supports native LIMIT; if not,
* it will be emulated. Limit only applies to queries (not update sql).
* @param int $v Maximum number of rows or 0 for all rows.
* @return void
*/
public function setLimit($v)
{
$this->limit = (int) $v;
}
/**
* Returns the maximum number of rows to return or 0 for all.
* @return int
*/
public function getLimit()
{
return $this->limit;
}
/**
* Sets the start row.
* This will affect the SQL if the RDBMS supports native OFFSET; if not,
* it will be emulated. Offset only applies to queries (not update) and
* only is evaluated when LIMIT is set!
* @param int $v
* @return void
*/
public function setOffset($v)
{
$this->offset = (int) $v;
}
/**
* Returns the start row.
* Offset only applies when Limit is set!
* @return int
*/
public function getOffset()
{
return $this->offset;
}
/**
* Free resources associated with this statement.
* Some drivers will need to implement this method to free
* database result resources.
*
* @return void
*/
public function close()
{
// do nothing here (subclasses will implement)
}
/**
* Generic execute() function has to check to see whether SQL is an update or select query.
*
* If you already know whether it's a SELECT or an update (manipulating) SQL, then use
* the appropriate method, as this one will incurr overhead to check the SQL.
*
* @param int $fetchmode Fetchmode (only applies to queries).
* @return boolean True if it is a result set, false if not or if no more results (this is identical to JDBC return val).
* @throws SQLException
* @todo -cStatementCommon Update execute() to not use isSelect() method, but rather to determine type based on returned results.
*/
public function execute($sql, $fetchmode = null)
{
if (!$this->isSelect($sql)) {
$this->updateCount = $this->executeUpdate($sql);
return false;
} else {
$this->resultSet = $this->executeQuery($sql, $fetchmode);
if ($this->resultSet->getRecordCount() === 0) {
return false;
}
return true;
}
}
/**
* Get result set.
* This assumes that the last thing done was an executeQuery() or an execute()
* with SELECT-type query.
*
* @return RestultSet (or null if none)
*/
public function getResultSet()
{
return $this->resultSet;
}
/**
* Get update count.
*
* @return int Number of records affected, or <code>null</code> if not applicable.
*/
public function getUpdateCount()
{
return $this->updateCount;
}
/**
* Returns whether the passed SQL is a SELECT statement.
*
* Returns true if SQL starts with 'SELECT' but not 'SELECT INTO'. This exists
* to support the execute() function -- which could either execute an update or
* a query.
*
* Currently this function does not take into consideration comments, primarily
* because there are a number of different comment options for different drivers:
* <pre>
* -- SQL-defined comment, but not truly comment in Oracle
* # comment in mysql
* /* comment in mssql, others * /
* // comment sometimes?
* REM also comment ...
* </pre>
*
* If you're wondering why we can't just execute the query and look at the return results
* to see whether it was an update or a select, the reason is that for update queries we
* need to do stuff before we execute them -- like start transactions if auto-commit is off.
*
* @param string $sql
* @return boolean Whether statement is a SELECT SQL statement.
* @see execute()
*/
protected function isSelect($sql)
{
// is first word is SELECT, then return true, unless it's SELECT INTO ...
// this doesn't, however, take comments into account ...
$sql = trim($sql);
return (stripos($sql, 'select') === 0 && stripos($sql, 'select into ') !== 0);
}
/**
* Executes the SQL query in this PreparedStatement object and returns the resultset generated by the query.
*
* @param string $sql This method may optionally be called with the SQL statement.
* @param int $fetchmode The mode to use when fetching the results (e.g. ResultSet::FETCHMODE_NUM, ResultSet::FETCHMODE_ASSOC).
* @return object Creole::ResultSet
* @throws SQLException If there is an error executing the specified query.
* @todo -cStatementCommon Put native query execution logic in statement subclasses.
*/
public function executeQuery($sql, $fetchmode = null)
{
$this->updateCount = null;
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 string $sql This method may optionally be called with the SQL statement.
* @return int Number of affected rows (or 0 for drivers that return nothing).
* @throws SQLException if a database access error occurs.
*/
public function executeUpdate($sql)
{
if ($this->resultSet) $this->resultSet->close();
$this->resultSet = null;
$this->updateCount = $this->conn->executeUpdate($sql);
return $this->updateCount;
}
/**
* Gets next result set (if this behavior is supported by driver).
* Some drivers (e.g. MSSQL) support returning multiple result sets -- e.g.
* from stored procedures.
*
* This function also closes any current restult set.
*
* Default behavior is for this function to return false. Driver-specific
* implementations of this class can override this method if they actually
* support multiple result sets.
*
* @return boolean True if there is another result set, otherwise false.
*/
public function getMoreResults()
{
if ($this->resultSet) $this->resultSet->close();
$this->resultSet = null;
return false;
}
/**
* Gets the db Connection that created this statement.
* @return Connection
*/
public function getConnection()
{
return $this->conn;
}
}