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,218 @@
<?php
/*
* $Id: ODBCCachedResultSet.php,v 1.2 2005/04/01 17:04:00 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>.
*/
require_once 'creole/drivers/odbc/ODBCResultSetCommon.php';
require_once 'creole/drivers/odbc/ODBCTypes.php';
/**
* ODBC implementation of a cached ResultSet.
*
* In addition to limit/offset emulation, this class implements a resultset
* cache. This can be useful as a workaround for some ODBC drivers which lack
* support for reverse/absolute cursor scrolling, etc.
*
* This class will cache rows _on-demand_. So if you only read the first couple
* rows of a result, then only those rows will be cached. However, note that if
* you call getRecordCount() or last(), the class must read and cache all
* available records.
*
* The offset / limit variables are also taken into account when caching. Any
* rows preceding the offset value will be skipped. Caching will stop once the
* limit value is reached.
*
* To use this class, create a derived {@link ODBCAdapter} class which returns
* an instance of ODBCCachedResultSet from the {@link ODBCAdapter::createResultSet()} method.
* Specify the adapter via the query portion of the Connection URL:
*
* odbc://localhost/Driver=MySQL ODBC 3.51 Driver;Database=test?adapter=MySQL
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.2 $
* @package creole.drivers.odbc
*/
class ODBCCachedResultSet extends ODBCResultSetCommon implements ResultSet
{
/**
* Record cache
* @var array
*/
protected $recs = array();
/**
* Tracks the last cursor position of the recordset.
* @var integer
*/
protected $lastPos = -1;
/**
* True if blobs/clobs should also be cached.
* @var boolean
*/
protected $cacheLobs = false;
/**
* @see ResultSet::__construct()
*/
public function __construct(Connection $conn, $result, $fetchmode = null, $cacheLobs = false)
{
parent::__construct($conn, $result, $fetchmode);
$this->cacheLobs = $cacheLobs;
}
/**
* @see ODBCResultSetCommon::close()
*/
function close()
{
parent::close();
$this->recs = null;
$this->lastPos = -1;
$this->cacheLobs = false;
}
/**
* Caches specified records up to and including the specified 1-based
* record position. If -1 is specified, all records will be cached.
* @param integer Maximum record position to cache.
* @return void
* @throws SQLException
*/
public function loadCache($recPos = -1)
{
$rid = $this->result->getHandle();
$curRecs = count($this->recs);
$totRecs = ($curRecs ? $this->offset + $curRecs : 0);
while (1)
{
// Is record already cached?
if ($this->lastPos != -1 || ($recPos > -1 && $recPos <= $curRecs))
return;
// Fetch row (no buffers copied yet).
$rowNum = ++$totRecs;
$result = @odbc_fetch_row($rid, $rowNum);
// All records cached?
if ($result === false || ($this->limit > 0 && $curRecs+1 > $this->limit))
{
$this->lastPos = $curRecs;
continue;
}
// Ignore offset records.
if ($totRecs <= $this->offset)
continue;
// Load row array.
$row = array();
for ($i = 0, $n = @odbc_num_fields($rid); $i < $n; $i++)
{
$fldNum = $i+1;
$row[$i] = odbc_result($rid, $fldNum);
// Cache lobs if necessary
if ($this->cacheLobs)
{
ODBCTypes::loadTypeMap($this->conn);
$nativeType = @odbc_field_type($rid, $fldNum);
$creoleType = ODBCTypes::getType($nativeType);
$isBlob = ($creoleType == CreoleTypes::BLOB ||
$creoleType == CreoleTypes::LONGVARBINARY);
$isClob = ($creoleType == CreoleTypes::CLOB ||
$creoleType == CreoleTypes::LONGVARCHAR);
if (($isBlob || $isClob) && $row[$i] !== null)
{
$binmode = ($isBlob ? ODBC_BINMODE_RETURN : ODBC_BINMODE_CONVERT);
$curdata = $row[$i];
$row[$i] = $this->readLobData($fldNum, $binmode, $curdata);
}
}
}
// Add record to cache.
$this->recs[++$curRecs] = $row;
}
}
/**
* @see ResultSet::seek()
*/
public function seek($rownum)
{
$this->loadCache($rownum);
if ($rownum < 0 || $rownum > count($this->recs)+1)
return false;
$this->cursorPos = $rownum;
return true;
}
/**
* @see ResultSet::next()
*/
function next()
{
$this->loadCache(++$this->cursorPos);
if ($this->isAfterLast())
{
$this->afterLast();
return false;
}
$this->fields =& $this->checkFetchMode($this->recs[$this->cursorPos]);
return true;
}
/**
* @see ResultSet::getRecordCount()
*/
function getRecordCount()
{
if ($this->lastPos == -1)
$this->loadCache(-1);
return $this->lastPos;
}
/**
* @see ResultSet::isAfterLast()
*/
public function isAfterLast()
{
// All records cached yet?
if ($this->lastPos == -1)
return false;
return ($this->cursorPos > $this->lastPos);
}
}

View File

@ -0,0 +1,362 @@
<?php
/*
* $Id: ODBCConnection.php,v 1.6 2006/01/17 19:44:39 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>.
*/
require_once 'creole/Connection.php';
require_once 'creole/common/ConnectionCommon.php';
require_once 'creole/drivers/odbc/adapters/ODBCAdapter.php';
/**
* ODBC implementation of Connection.
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.6 $
* @package creole.drivers.odbc
*/
class ODBCConnection extends ConnectionCommon implements Connection {
/**
* Implements driver-specific behavior
* @var ODBCAdapter
*/
protected $adapter = null;
/**
* Last ODBC result resource from executeQuery/executeUpdate. Used in getUpdateCount()
* @var ODBCResultResource
*/
protected $odbcresult = null;
/**
* @see Connection::connect()
*/
public function connect($dsninfo, $flags = 0)
{
if (!function_exists('odbc_connect'))
throw new SQLException('odbc extension not loaded');
$adapterclass = isset($dsninfo['adapter']) ? $dsninfo['adapter'] : null;
if (!$adapterclass)
$adapterclass = 'ODBCAdapter';
else
$adapterclass .= 'Adapter';
Creole::import('creole.drivers.odbc.adapters.' . $adapterclass);
$this->adapter = new $adapterclass();
$this->dsn = $dsninfo;
$this->flags = $flags;
if ( !($this->flags & Creole::COMPAT_ASSOC_LOWER) && !$this->adapter->preservesColumnCase())
{
trigger_error('Connection created without Creole::COMPAT_ASSOC_LOWER, ' .
'but driver does not support case preservation.',
E_USER_WARNING);
$this->flags != Creole::COMPAT_ASSOC_LOWER;
}
$persistent = ($flags & Creole::PERSISTENT) === Creole::PERSISTENT;
if ($dsninfo['database'])
$odbcdsn = $dsninfo['database'];
elseif ($dsninfo['hostspec'])
$odbcdsn = $dsninfo['hostspec'];
else
$odbcdsn = 'localhost';
$user = @$dsninfo['username'];
$pw = @$dsninfo['password'];
$connect_function = $persistent ? 'odbc_pconnect' : 'odbc_connect';
$conn = @$connect_function($odbcdsn, $user, $pw, SQL_CUR_USE_IF_NEEDED);
if (!is_resource($conn))
throw new SQLException('connect failed', $this->nativeError(), $odbcdsn);
$this->dblink = $conn;
/**
* This prevents blob fields from being fetched when a row is loaded
* from a recordset. Clob fields however are loaded with up to
* 'odbc.defaultlrl' data. This should be the default anyway, but we'll
* set it here just to keep things consistent.
*/
@odbc_binmode(0, ODBC_BINMODE_PASSTHRU);
@odbc_longreadlen(0, ini_get('odbc.defaultlrl'));
}
/**
* @see Connection::close()
*/
public function close()
{
$ret = true;
$this->adapter = null;
$this->odbcresult = null;
if ($this->dblink !== null)
{
$ret = @odbc_close($this->dblink);
$this->dblink = null;
}
return $ret;
}
/**
* Shouldn't this be in ConnectionCommon.php?
*/
public function __destruct()
{
$this->close();
}
/**
* Returns a formatted ODBC error string.
* @return string
*/
public function nativeError()
{
if ($this->dblink && is_resource($this->dblink))
$errstr = '[' . @odbc_error($this->dblink) . '] ' . @odbc_errormsg($this->dblink);
else
$errstr = '[' . @odbc_error() . '] ' . @odbc_errormsg();
return $errstr;
}
/**
* Returns driver-specific ODBCAdapter.
* @return ODBCAdapter
*/
public function getAdapter()
{
return $this->adapter;
}
/**
* @see Connection::getDatabaseInfo()
*/
public function getDatabaseInfo()
{
require_once 'creole/drivers/odbc/metadata/ODBCDatabaseInfo.php';
return new ODBCDatabaseInfo($this);
}
/**
* @see Connection::getIdGenerator()
*/
public function getIdGenerator()
{
return $this->adapter->getIdGenerator($this);
}
/**
* Creates the appropriate ResultSet
* @return ResultSet
*/
public function createResultSet($odbcresult, $fetchmode)
{
return $this->adapter->createResultSet($this, $odbcresult, $fetchmode);
}
/**
* @see Connection::prepareStatement()
*/
public function prepareStatement($sql)
{
require_once 'creole/drivers/odbc/ODBCPreparedStatement.php';
return new ODBCPreparedStatement($this, $sql);
}
/**
* @see Connection::createStatement()
*/
public function createStatement()
{
require_once 'creole/drivers/odbc/ODBCStatement.php';
return new ODBCStatement($this);
}
/**
* @todo To be implemented
* @see Connection::prepareCall()
*/
public function prepareCall($sql)
{
throw new SQLException('Stored procedures not currently implemented.');
}
/**
* @see Connection::applyLimit()
*/
public function applyLimit(&$sql, $offset, $limit)
{
if ($this->adapter->hasLimitOffset())
$this->adapter->applyLimit($sql, $offset, $limit);
}
/**
* @see Connection::executeQuery()
*/
public function executeQuery($sql, $fetchmode = null)
{
if ($this->odbcresult)
$this->odbcresult = null;
$r = @odbc_exec($this->dblink, $sql);
if ($r === false)
throw new SQLException('Could not execute query', $this->nativeError(), $sql);
$this->odbcresult = new ODBCResultResource($r);
return $this->createResultSet($this->odbcresult, $fetchmode);
}
/**
* @see Connection::executeUpdate()
*/
public function executeUpdate($sql)
{
if ($this->odbcresult)
$this->odbcresult = null;
$r = @odbc_exec($this->dblink, $sql);
if ($r === false)
throw new SQLException('Could not execute update', $this->nativeError(), $sql);
$this->odbcresult = new ODBCResultResource($r);
return $this->getUpdateCount();
}
/**
* Start a database transaction.
* @throws SQLException
* @return void
*/
protected function beginTrans()
{
if ($this->adapter->supportsTransactions()) {
@odbc_autocommit($this->dblink, false);
if (odbc_error($this->dblink) == 'S1C00') {
throw new SQLException('Could not begin transaction', $this->nativeError());
}
}
}
/**
* Commit the current transaction.
* @throws SQLException
* @return void
*/
protected function commitTrans()
{
if ($this->adapter->supportsTransactions()) {
$result = @odbc_commit($this->dblink);
if (!$result) {
throw new SQLException('Could not commit transaction', $this->nativeError());
}
@odbc_autocommit($this->dblink, true);
if (odbc_error($this->dblink) == 'S1C00') {
throw new SQLException('Could not commit transaction (autocommit failed)', $this->nativeError());
}
}
}
/**
* Roll back (undo) the current transaction.
* @throws SQLException
* @return void
*/
protected function rollbackTrans()
{
if ($this->adapter->supportsTransactions()) {
$result = @odbc_rollback($this->dblink);
if (!$result) {
throw new SQLException('Could not rollback transaction', $this->nativeError());
}
@odbc_autocommit($this->dblink, true);
if (odbc_error($this->dblink) == 'S1C00') {
throw new SQLException('Could not rollback transaction (autocommit failed)', $this->nativeError());
}
}
}
/**
* @see Connection::getUpdateCount()
*/
public function getUpdateCount()
{
if ($this->odbcresult === null)
return 0;
$n = @odbc_num_rows($this->odbcresult->getHandle());
if ($n == -1)
throw new SQLException('Could not retrieve update count', $this->nativeError());
return (int) $n;
}
}
/**
* This is a simple wrapper class to manage the lifetime of an ODBC result resource
* (returned by odbc_exec(), odbc_execute(), etc.) We use a separate class because
* the resource can be shared by both ODBCConnection and an ODBCResultSet at the
* same time. ODBCConnection hangs on to the last result resource to be used in
* its getUpdateCount() method. It also passes this resource to new instances of
* ODBCResultSet. At some point the resource has to be cleaned up via
* odbc_free_result(). Using this class as a wrapper, we can pass around multiple
* references to the same resource. PHP's reference counting mechanism will clean
* up the resource when its no longer used via ODBCResultResource::__destruct().
* @package creole.drivers.odbc
*/
class ODBCResultResource
{
/**
* @var resource ODBC result resource returned by {@link odbc_exec()}/{@link odbc_execute()}.
*/
protected $handle = null;
public function __construct($handle)
{
if (is_resource($handle))
$this->handle = $handle;
}
public function __destruct()
{
if ($this->handle !== null)
@odbc_free_result($this->handle);
}
public function getHandle()
{
return $this->handle;
}
}

View File

@ -0,0 +1,118 @@
<?php
require_once 'creole/IdGenerator.php';
/**
* ODBC IdGenerator implimenation.
*
* NOTE: I tried keeping the SQL as basic as possible in this class.
* If you need something more optimized, derive your own IdGenerator
* and use {@link ODBCAdapter::getIdGenerator()} to use it.
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.2 $
* @package creole.drivers.odbc
*/
class ODBCIdGenerator implements IdGenerator {
/** Connection object that instantiated this class */
private $conn;
/**
* Creates a new IdGenerator class, saves passed connection for use
* later by getId() method.
* @param Connection $conn
*/
public function __construct(Connection $conn)
{
$this->conn = $conn;
}
/**
* @see IdGenerator::isBeforeInsert()
*/
public function isBeforeInsert()
{
return true;
}
/**
* @see IdGenerator::isAfterInsert()
*/
public function isAfterInsert()
{
return false;
}
/**
* @see IdGenerator::getIdMethod()
*/
public function getIdMethod()
{
return self::SEQUENCE;
}
/**
* @see IdGenerator::getId()
*/
public function getId($seqname = null)
{
if ($seqname === null)
throw new SQLException('You must specify the sequence name when calling getId() method.');
$triedcreate = false;
while (1)
{
try
{
$n = $this->conn->executeUpdate("UPDATE $seqname SET id = id + 1", ResultSet::FETCHMODE_NUM);
if ($n == 0)
throw new SQLException('Failed to update IdGenerator id', $this->conn->nativeError());
$rs = $this->conn->executeQuery("SELECT id FROM $seqname", ResultSet::FETCHMODE_NUM);
}
catch (SQLException $e)
{
//$odbcerr = odbc_error($this->conn->getResource());
if ($triedcreate)// || ($odbcerr != 'S0000' && $odbcerr != 'S0002'))
throw $e;
$this->drop($seqname, true);
$this->create($seqname);
$triedcreate = true;
continue;
}
break;
}
$rs->first();
return $rs->getInt(1);
}
/**
* Creates the sequence emulation table.
*/
public function create($seqname)
{
$this->conn->executeUpdate("CREATE TABLE $seqname ( id numeric(19,0) NOT NULL )");
$this->conn->executeUpdate("INSERT INTO $seqname ( id ) VALUES ( 0 )");
}
/**
* Drops the sequence emulation table.
*/
public function drop($seqname, $ignoreerrs = false)
{
try {
$this->conn->executeUpdate("DROP TABLE $seqname");
} catch (Exception $e) {
if (!$ignoreerrs) throw $e;
}
}
}

View File

@ -0,0 +1,246 @@
<?php
/*
* $Id: ODBCPreparedStatement.php,v 1.4 2005/11/13 01:29:01 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>.
*/
require_once 'creole/PreparedStatement.php';
require_once 'creole/common/PreparedStatementCommon.php';
require_once 'creole/util/Lob.php';
/**
* ODBC specific PreparedStatement functions.
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.4 $
* @package creole.drivers.odbc
*/
class ODBCPreparedStatement extends PreparedStatementCommon implements PreparedStatement
{
/**
* This does nothing since ODBC natively supports prepared statements.
* @see PreparedStatementCommon::replaceParams()
*/
protected function replaceParams()
{
if ($this->conn->getAdapter()->emulatePrepareStmt())
return parent::replaceParams();
else
return $this->sql;
}
/**
* Internal function to call native ODBC prepare/execute functions.
*/
protected function _execute($sql, $params, $fetchmode, $isupdate)
{
if ($this->resultSet)
{
$this->resultSet->close();
$this->resultSet = null;
}
$this->updateCount = null;
if ($this->conn->getAdapter()->emulatePrepareStmt())
{
$stmt = @odbc_exec($this->conn->getResource(), $sql);
$ret = ($stmt !== false);
}
else
{
// Trim surrounding quotes added from default set methods.
// Exception: for LOB-based parameters, odbc_execute() will
// accept a filename surrounded by single-quotes.
foreach ($this->boundInVars as $idx => $var)
{
if ($var instanceof Lob)
{
$file = ($isupdate ? $var->getInputFile() : $var->getOutputFile());
$this->boundInVars[$idx] = "'$file'";
}
else if (is_string($var))
{
$this->boundInVars[$idx] = trim($var, "\"\'");
}
}
$stmt = @odbc_prepare($this->conn->getResource(), $sql);
if ($stmt === FALSE)
throw new SQLException('Could not prepare query', $this->conn->nativeError(), $sql);
$ret = @odbc_execute($stmt, $this->boundInVars);
}
if ($ret === FALSE)
{
@odbc_free_result($stmt);
throw new SQLException('Could not execute query', $this->conn->nativeError(), $sql);
}
return $this->conn->createResultSet(new ODBCResultResource($stmt), $fetchmode);
}
/**
* @see PreparedStatement::executeQuery()
*/
public function executeQuery()
{
switch (func_num_args()) {
case 2:
list($params, $fetchmode) = func_get_args();
if (!is_array($params)) {
unset($params);
}
break;
case 1:
$params = null;
list($fetchmode) = func_get_args();
break;
case 0:
$params = null;
$fetchmode = null;
break;
}
// Set any params passed directly
if (isset($params)) {
for($i=0,$cnt=count($params); $i < $cnt; $i++) {
$this->set($i+1, $params[$i]);
}
}
$sql = $this->replaceParams();
if ($this->conn->getAdapter()->hasLimitOffset())
{
if ($this->limit > 0 || $this->offset > 0)
$this->conn->applyLimit($sql, $this->offset, $this->limit);
}
$this->resultSet = $this->_execute($sql, $params, $fetchmode, false);
if (!$this->conn->getAdapter()->hasLimitOffset())
{
$this->resultSet->_setOffset($this->offset);
$this->resultSet->_setLimit($this->limit);
}
return $this->resultSet;
}
/**
* @see PreparedStatement::executeUpdate()
*/
public function executeUpdate($params = null)
{
// Set any params passed directly
if ($params) {
for($i=0,$cnt=count($params); $i < $cnt; $i++) {
$this->set($i+1, $params[$i]);
}
}
$sql = $this->replaceParams();
$this->_execute($sql, $params, 0, true);
$this->updateCount = $this->conn->getUpdateCount();
return $this->updateCount;
}
/**
* @see PreparedStatementCommon::escape()
*/
protected function escape($str)
{
if ($this->conn->getAdapter()->emulatePrepareStmt())
return $this->conn->getAdapter()->escape($str);
// Nothing to do here. odbc_execute() takes care of escaping strings.
return $str;
}
/**
* @see PreparedStatement::setNull()
*/
function setNull($paramIndex)
{
$this->sql_cache_valid = false;
$this->boundInVars[$paramIndex] = null;
}
/**
* @see PreparedStatement::setBlob()
*/
function setBlob($paramIndex, $blob)
{
if ($this->conn->getAdapter()->emulatePrepareStmt())
return parent::setBlob($paramIndex, $blob);
$this->sql_cache_valid = false;
if ($blob === null)
{
$this->setNull($paramIndex);
return;
}
if ($blob instanceof Blob)
{
if ($blob->isFromFile() && !$blob->isModified())
{
$this->boundInVars[$paramIndex] = $blob;
return;
}
$blob = $blob->__toString();
}
$this->boundInVars[$paramIndex] = "'" . $this->escape($blob) . "'";
}
/**
* @see PreparedStatement::setClob()
*/
function setClob($paramIndex, $clob)
{
if ($this->conn->getAdapter()->emulatePrepareStmt())
return parent::setClob($paramIndex, $clob);
$this->sql_cache_valid = false;
if ($clob === null)
{
$this->setNull($paramIndex);
return;
}
if ($clob instanceof Clob)
{
if ($clob->isFromFile() && !$clob->isModified())
{
$this->boundInVars[$paramIndex] = $clob;
return;
}
$clob = $clob->__toString();
}
$this->boundInVars[$paramIndex] = "'" . $this->escape($clob) . "'";
}
}

View File

@ -0,0 +1,209 @@
<?php
/*
* $Id: ODBCResultSet.php,v 1.2 2005/04/01 17:10:42 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>.
*/
require_once 'creole/drivers/odbc/ODBCResultSetCommon.php';
/**
* ODBC implementation of ResultSet.
*
* If the current ODBC driver does not support LIMIT or OFFSET natively,
* the methods in here perform some adjustments and extra checking to make
* sure that this behaves the same as RDBMS drivers using native OFFSET/LIMIT.
*
* This class also emulates a row count if the driver is not capable of
* providing one natively.
*
* NOTE: This class only works with drivers that support absolute cursor
* positioning (SQL_FETCH_DIRECTION = SQL_FD_FETCH_ABSOLUTE). If the
* driver you are using does not support reverse/absolute cursor
* scrolling, you should use the {@link ODBCCachedResultSet} class instead.
* See the documentation for ODBCCachedResultSet for instructions on how
* to use it.
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.2 $
* @package creole.drivers.odbc
*/
class ODBCResultSet extends ODBCResultSetCommon implements ResultSet
{
/**
* Number of rows in resultset.
*
* @var int
*/
protected $numRows = -1;
/**
* True if ODBC driver supports odbc_num_rows().
*
* @var boolean
*/
protected $hasRowCount = false;
/**
* @see ResultSet::__construct()
*/
public function __construct(Connection $conn, $result, $fetchmode = null)
{
parent::__construct($conn, $result, $fetchmode);
/**
* Some ODBC drivers appear not to handle odbc_num_rows() very well when
* more than one result handle is active at once. For example, the MySQL
* ODBC driver always returns the number of rows for the last executed
* result. For this reason, we'll store the row count here.
*
* Note also that many ODBC drivers do not support this method. In this
* case, getRecordCount() will perform a manual count.
*/
$this->numRows = @odbc_num_rows($result->getHandle());
$this->hasRowCount = $this->numRows != -1;
}
/**
* @see ODBCResultSetCommon::close()
*/
function close()
{
parent::close();
$numRows = -1;
}
/**
* @see ResultSet::seek()
*/
public function seek($rownum)
{
if ($rownum < 0 || $this->limit > 0 && $rownum > $this->limit)
return false;
$this->cursorPos = $rownum;
return true;
}
/**
* @see ResultSet::next()
*/
public function next()
{
$this->cursorPos++;
if ($this->limit > 0 && $this->cursorPos > $this->limit) {
$this->cursorPos = $this->limit+1;
return false;
}
$rowNum = $this->offset + $this->cursorPos;
$fields = null;
$cols = @odbc_fetch_into($this->result->getHandle(), $fields, $rowNum);
if ($cols === false) {
$this->cursorPos = -1;
return false;
}
$this->fields =& $this->checkFetchMode($fields);
return true;
}
/**
* @see ResultSet::isAfterLast()
*/
public function isAfterLast()
{
// Force calculation of last record pos.
if ($this->cursorPos == -1)
$this->getRecordCount();
return parent::isAfterLast();
}
/**
* @see ResultSet::getRecordCount()
*/
function getRecordCount()
{
if ($this->hasRowCount)
{
// Use driver row count if provided.
$numRows = $this->numRows - $this->offset;
if ($this->limit > 0 && $numRows > $this->limit)
$numRows = $this->limit;
}
else
{
// Do manual row count if driver doesn't provide one.
if ($this->numRows == -1)
{
$this->numRows = 0;
$this->beforeFirst();
while($this->next())
$this->numRows++;
}
$numRows = $this->numRows;
}
// Cursor pos is -1 when an attempt to fetch past the last row was made
// (or a fetch error occured).
if ($this->cursorPos == -1)
$this->cursorPos = $numRows+1;
return $numRows;
}
/**
* @see ResultSet::getBlob()
*/
public function getBlob($column)
{
require_once 'creole/util/Blob.php';
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
$data = $this->readLobData($column, ODBC_BINMODE_RETURN, $this->fields[$idx]);
if (!$data) { return null; }
$b = new Blob();
$b->setContents($data);
return $b;
}
/**
* @see ResultSet::getClob()
*/
public function getClob($column)
{
require_once 'creole/util/Clob.php';
$idx = (is_int($column) ? $column - 1 : $column);
if (!array_key_exists($idx, $this->fields)) { throw new SQLException("Invalid resultset column: " . $column); }
$data = $this->readLobData($column, ODBC_BINMODE_CONVERT, $this->fields[$idx]);
if (!$data) { return null; }
$c = new Clob();
$c->setContents($data);
return $c;
}
}

View File

@ -0,0 +1,188 @@
<?php
/*
* $Id: ODBCResultSetCommon.php,v 1.3 2006/01/17 19:44:39 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>.
*/
require_once 'creole/ResultSet.php';
require_once 'creole/common/ResultSetCommon.php';
/**
* Base class for ODBC implementation of ResultSet.
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.3 $
* @package creole.drivers.odbc
*/
abstract class ODBCResultSetCommon extends ResultSetCommon
{
/**
* Offset at which to start reading rows (for emulated offset).
* @var int
*/
protected $offset = 0;
/**
* Maximum rows to retrieve, or 0 if all (for emulated limit).
* @var int
*/
protected $limit = 0;
/**
* @see ResultSet::__construct()
*/
public function __construct(Connection $conn, $result, $fetchmode = null)
{
parent::__construct($conn, $result, $fetchmode);
}
/**
* @see ResultSet::close()
*/
public function close()
{
$this->result = null;
$this->conn = null;
$this->fetchmode = null;
$this->cursorPos = 0;
$this->fields = null;
$this->lowerAssocCase = false;
$this->limit = 0;
$this->offset = 0;
}
/**
* This function exists to set offset after ResultSet is instantiated.
* This function should be "protected" in Java sense: only available to classes in package.
* THIS METHOD SHOULD NOT BE CALLED BY ANYTHING EXCEPTION DRIVER CLASSES.
* @param int $offset New offset.
* @access protected
*/
public function _setOffset($offset)
{
$this->offset = $offset;
}
/**
* This function exists to set limit after ResultSet is instantiated.
* This function should be "protected" in Java sense: only available to classes in package.
* THIS METHOD SHOULD NOT BE CALLED BY ANYTHING EXCEPTION DRIVER CLASSES.
* @param int $limit New limit.
* @access protected
*/
public function _setLimit($limit)
{
$this->limit = $limit;
}
/**
* If fetchmode is FETCHMODE_ASSOC, returns the 1-based field index number
* for the specified column name. Otherwise returns 0 (false).
* @return int
*/
function getFieldNum($colname)
{
$fieldnum = 0;
if ($this->fetchmode == ResultSet::FETCHMODE_ASSOC)
{
$keys = array_keys($this->fields);
$fieldnum = array_search($colname, $keys);
}
return $fieldnum;
}
/**
* Reads in any unread LOB data. For long char fields, we may already
* have up to odbc_longreadlen() bytes in the buffer. These are passed
* in via the $curdata parm. For long binary fields, no data is read
* initially since odbc_binmode() is set to ODBC_BINMODE_PASSTHRU.
* This method adjusts the binmode and longreadlen to finish reading
* these datatypes into the buffer. Returns a string with the complete
* contents.
*
* @param int|string $column Column index or name to read data from.
* @param int $binmode ODBC_BINMODE_RETURN for binary data, ODBC_BINMODE_CONVERT for char data.
* @param string $curdata Existing LOB data already in buffer.
* @return string
*/
protected function readLobData($column, $binmode, $curdata = null)
{
// Retrieve field num
$fldNum = (is_int($column) ? $column : getFieldNum($column));
$data = $curdata;
$newdata = null;
// Adjust binmode and longreadlen
odbc_binmode($this->result->getHandle(), $binmode);
odbc_longreadlen($this->result->getHandle(), 4096);
while (1)
{
$newdata = odbc_result($this->result->getHandle(), $fldNum);
if ($newdata === false)
break;
else
$data .= $newdata;
}
// Restore the default binmode and longreadlen
odbc_binmode($this->result->getHandle(), ODBC_BINMODE_PASSTHRU);
odbc_longreadlen($this->result->getHandle(), ini_get('odbc.defaultlrl'));
// The ODBC driver I use seems to return a string with an escaped
// null char at the end for clob data.
$data = rtrim($data, "\x0");
return $data;
}
/**
* Converts row fields to names if FETCHMODE_ASSOC is set.
*
* @param array& Row to convert.
*
* @return array& Converted row.
*/
protected function checkFetchMode(&$row)
{
if ($this->fetchmode == ResultSet::FETCHMODE_ASSOC)
{
$newrow = array();
for ($i = 0, $n = count($row); $i < $n; $i++)
{
$colname = @odbc_field_name($this->result->getHandle(), $i+1);
if ($this->lowerAssocCase) {
$colname = strtolower($colname);
}
$newrow[$colname] = $row[$i];
}
$row =& $newrow;
}
return $row;
}
}

View File

@ -0,0 +1,64 @@
<?php
/*
* $Id: ODBCStatement.php,v 1.1 2004/07/27 23:08:30 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>.
*/
require_once 'creole/Statement.php';
require_once 'creole/common/StatementCommon.php';
/**
* ODBC Statement
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.1 $
* @package creole.drivers.odbc
*/
class ODBCStatement extends StatementCommon implements Statement
{
/**
* @see Statement::executeQuery()
*/
public function executeQuery($sql, $fetchmode = null)
{
if ($this->resultSet)
{
$this->resultSet->close();
$this->resultSet = null;
}
$this->updateCount = null;
if ($this->conn->getAdapter()->hasLimitOffset())
{
if ($this->limit > 0 || $this->offset > 0)
$this->conn->applyLimit($sql, $this->offset, $this->limit);
}
$this->resultSet = $this->conn->executeQuery($sql, $fetchmode);
if (!$this->conn->getAdapter()->hasLimitOffset())
{
$this->resultSet->_setOffset($this->offset);
$this->resultSet->_setLimit($this->limit);
}
return $this->resultSet;
}
}

View File

@ -0,0 +1,189 @@
<?php
/*
* $Id: ODBCTypes.php,v 1.1 2004/07/27 23:08:30 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>.
*/
require_once 'creole/CreoleTypes.php';
/**
* ODBC types / type map.
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.1 $
* @package creole.drivers.odbc
*/
class ODBCTypes extends CreoleTypes {
/**
* Map ODBC native types to Creole (JDBC) types.
*/
protected static $typeMap = null;
/**
* Reverse mapping, created on demand.
*/
protected static $reverseMap = null;
/**
* Loads the map of ODBC data types to Creole (JDBC) types.
*
* NOTE: This function cannot map DBMS-specific datatypes. If you use a
* driver which implements DBMS-specific datatypes, you will need
* to modify/extend this class to add the correct mapping.
*/
public static function loadTypeMap($conn = null)
{
if (self::$typeMap !== null && count(self::$typeMap) > 0)
return;
if ($conn == null)
throw new SQLException('No connection specified when loading ODBC type map.');
self::$typeMap = array();
$result = @odbc_gettypeinfo($conn->getResource());
if ($result === false)
throw new SQLException('Failed to retrieve type info.', $conn->nativeError());
$rowNum = 1;
while (odbc_fetch_row($result, $rowNum++))
{
$odbctypeid = odbc_result($result, 'DATA_TYPE');
$odbctypename = odbc_result($result, 'TYPE_NAME');
switch ($odbctypeid)
{
case SQL_CHAR:
self::$typeMap[$odbctypename] = CreoleTypes::CHAR;
break;
case SQL_VARCHAR:
self::$typeMap[$odbctypename] = CreoleTypes::VARCHAR;
break;
case SQL_LONGVARCHAR:
self::$typeMap[$odbctypename] = CreoleTypes::LONGVARCHAR;
break;
case SQL_DECIMAL:
self::$typeMap[$odbctypename] = CreoleTypes::DECIMAL;
break;
case SQL_NUMERIC:
self::$typeMap[$odbctypename] = CreoleTypes::NUMERIC;
break;
case SQL_BIT:
self::$typeMap[$odbctypename] = CreoleTypes::BOOLEAN;
break;
case SQL_TINYINT:
self::$typeMap[$odbctypename] = CreoleTypes::TINYINT;
break;
case SQL_SMALLINT:
self::$typeMap[$odbctypename] = CreoleTypes::SMALLINT;
break;
case SQL_INTEGER:
self::$typeMap[$odbctypename] = CreoleTypes::INTEGER;
break;
case SQL_BIGINT:
self::$typeMap[$odbctypename] = CreoleTypes::BIGINT;
break;
case SQL_REAL:
self::$typeMap[$odbctypename] = CreoleTypes::REAL;
break;
case SQL_FLOAT:
self::$typeMap[$odbctypename] = CreoleTypes::FLOAT;
break;
case SQL_DOUBLE:
self::$typeMap[$odbctypename] = CreoleTypes::DOUBLE;
break;
case SQL_BINARY:
self::$typeMap[$odbctypename] = CreoleTypes::BINARY;
break;
case SQL_VARBINARY:
self::$typeMap[$odbctypename] = CreoleTypes::VARBINARY;
break;
case SQL_LONGVARBINARY:
self::$typeMap[$odbctypename] = CreoleTypes::LONGVARBINARY;
break;
case SQL_DATE:
self::$typeMap[$odbctypename] = CreoleTypes::DATE;
break;
case SQL_TIME:
self::$typeMap[$odbctypename] = CreoleTypes::TIME;
break;
case SQL_TIMESTAMP:
self::$typeMap[$odbctypename] = CreoleTypes::TIMESTAMP;
break;
case SQL_TYPE_DATE:
self::$typeMap[$odbctypename] = CreoleTypes::DATE;
break;
case SQL_TYPE_TIME:
self::$typeMap[$odbctypename] = CreoleTypes::TIME;
break;
case SQL_TYPE_TIMESTAMP:
self::$typeMap[$odbctypename] = CreoleTypes::TIMESTAMP;
break;
default:
self::$typeMap[$odbctypename] = CreoleTypes::OTHER;
break;
}
}
@odbc_free_result($result);
}
/**
* This method returns the generic Creole (JDBC-like) type
* when given the native db type.
* @param string $nativeType DB native type (e.g. 'TEXT', 'byetea', etc.).
* @return int Creole native type (e.g. CreoleTypes::LONGVARCHAR, CreoleTypes::BINARY, etc.).
*/
public static function getType($nativeType)
{
if (!self::$typeMap)
self::loadTypeMap();
$t = strtoupper($nativeType);
if (isset(self::$typeMap[$t])) {
return self::$typeMap[$t];
} else {
return CreoleTypes::OTHER;
}
}
/**
* This method will return a native type that corresponds to the specified
* Creole (JDBC-like) type.
* If there is more than one matching native type, then the LAST defined
* native type will be returned.
* @param int $creoleType
* @return string Native type string.
*/
public static function getNativeType($creoleType)
{
if (!self::$typeMap)
self::loadTypeMap();
if (self::$reverseMap === null) {
self::$reverseMap = array_flip(self::$typeMap);
}
return @self::$reverseMap[$creoleType];
}
}

90
lib/symfony/vendor/creole/drivers/odbc/README vendored Executable file
View File

@ -0,0 +1,90 @@
Creole ODBC Bridge Driver
=========================
I. Overview
-----------
In the text below, the word "driver" can get somewhat muddled since there are
two libraries concerned here (Creole & ODBC). So, we'll use the term "bridge
driver" to refer to Creole's ODBC bridge driver, and "ODBC driver" to refer to
an ODBC database driver.
The Creole ODBC Bridge driver provides a solution for databases which
currently have no PHP-native interface. It is currently in an experimental
stage of development. It has been tested with two ODBC drivers (Sequiter's
CodeBase ODBC driver and the MySQL ODBC driver (as a baseline test)). To
use any other ODBC drivers you may need to write your own ODBCAdapter-derived
class (see below).
II. ODBCAdapter
---------------
Because ODBC itself is a database abstraction library, the bridge driver needed
a way of hiding ODBC driver-specific behavior. The solution to this was to
create an adapter layer (akin to how the Propel runtime engine works). Think of
it as a sub-driver for the bridge driver. Any ODBC driver-specific behavior is
handled by an ODBCAdapter-derived class. To use a specific adapter class, you
specify its name via a parameter in the connection string:
odbc://localhost/DSN=CodeBase;?adapter=CodeBase
The string above will load the following file as the adapter to use with the
bridge driver: creole/drivers/odbc/adapters/CodeBaseAdapter.php
Some ODBC drivers are limited in support for various Creole features. The
ODBCAdapter also provides a method for emulation of some of these missing
features:
-The emulatePrepareStmt() method provides a switch for enabling prepared
statement emulation for drivers that do not support (or have trouble with)
prepared statements. This emulation is disabled by default.
-The hasLimitOffset() method provides a switch for enabling LIMIT/OFFSET
emulation for drivers that do not support this. This emulation is enabled
by default. The LIMIT/OFFSET emulation was borrowed from the MSSQL Creole
driver.
-The createResultSet() method provides a switch for enabling cached
result sets. To enable this feature, return an instance of
ODBCCachedResultSet in the createResultSet() method of your ODBCAdapter-
derived class. This can be useful as a workaround for ODBC drivers which
lack support for record count retrieval, reverse/absolute cursor
scrolling, etc. In most cases, result rows are cached on-demand. So if
you only read the first couple rows of a result, then only those rows will
be cached.
-The getIdGenerator() method provides a switch for enabling sequence
emulation. This feature is enabled by default in ODBCAdapter and is
implemented in the ODBCIdGenerator class. The emulation code was inspired
by the PEAR::DB nextID() method. If your database supports sequences or
autoincrement natively, you can return your own IdGenerator-derived class
instead. Check out some of the other Creole drivers for IdGenerator
examples.
III. Incomplete Features
------------------------
-The database metadata classes are not fully tested/complete. Specifically,
the ODBCDatabaseInfo class does not currently set the database name. There
may be other problems as well.
-The Creole CallableStatement class (stored procedures) is not currently
implemented. No immediate plans to do this in the future, but it looks
feasible.
IV. Known Issues
----------------
This driver was developed using the PHP v5.0 final build. During the course
of testing I uncovered several bugs in the php_odbc module. I submitted
patches for these bugs, but have not yet received word that they were
committed (they were just submitted this morning). If you want more details
on the problems I encountered or would like a copy of the patches, please
e-mail me (dlawson@masterytech.com).

View File

@ -0,0 +1,73 @@
<?php
/*
* $Id: CodeBaseAdapter.php,v 1.3 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>.
*/
require_once 'creole/drivers/odbc/adapters/ODBCAdapter.php';
/**
* CodeBase driver-specific behavior.
*
* This adapter is for Sequiter's CodeBaseSQL product. It is a dBase ODBC
* driver. The driver only supports forward-only cursor scrolling so this
* adapter causes the ODBCCachedResultSet to be used.
*
* A couple other quirks exist:
*
* 1) Cannot get blobs to work correctly. If I try writing one to a
* LONGVARBINARY typed field, only the first few bytes are written.
* This will cause the ResultSetTest::testGetBlob() test case to fail
* when running tests for the driver.
*
* 2) For some reason the character count is off for the
* ResultSetTest::testSetClob() test case _only_ when running from the
* command line. If I run the same test through a web server it works fine.
* Looks like it has something to do with line endings in Windows. The
* difference in file sizes is 9803 vs 10090.
*
* 3) Setting a clob field to null writes a space to the field in the table.
* This causes the PreparedStatementTest::testSetNull() test case to fail
* when running tests for the driver.
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.3 $
* @package creole.drivers.odbc
*/
class CodeBaseAdapter extends ODBCAdapter
{
/**
* @see ODBCAdapter::createResultSet()
*/
public function preservesColumnCase()
{
return false;
}
/**
* @see ODBCAdapter::createResultSet()
*/
public function createResultSet($conn, $odbcresult, $fetchmode)
{
require_once 'creole/drivers/odbc/ODBCResultSet.php';
return new ODBCResultSet($conn, $odbcresult, $fetchmode, true);
}
}
?>

View File

@ -0,0 +1,78 @@
<?php
/*
* $Id: MySQLAdapter.php,v 1.1 2004/07/27 23:08:30 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>.
*/
require_once 'creole/drivers/odbc/ODBCCachedResultSet.php';
require_once 'creole/drivers/odbc/ODBCResultSet.php';
require_once 'creole/drivers/odbc/adapters/ODBCAdapter.php';
/**
* Implements MySQL driver-specific behavior.
*
* Obviously it would be much more efficient to simply use the Creole
* MySQL driver. This adapter was created for the sole purpose of testing
* the ODBC driver.
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.1 $
* @package creole.drivers.odbc
*/
class MySQLAdapter extends ODBCAdapter
{
/**
* @see ODBCAdapter::hasLimitOffset()
*/
public function hasLimitOffset()
{
return true;
}
/**
* @see ODBCAdapter::applyLimit()
*/
public function applyLimit(&$sql, $offset, $limit)
{
if ( $limit > 0 ) {
$sql .= " LIMIT " . ($offset > 0 ? $offset . ", " : "") . $limit;
} else if ( $offset > 0 ) {
$sql .= " LIMIT " . $offset . ", 18446744073709551615";
}
}
/**
* @see ODBCAdapter::escape()
*/
public function escape($str)
{
return addslashes($str);
}
/**
* @see ODBCAdapter::createResultSet()
*/
public function createResultSet($conn, $odbcresult, $fetchmode)
{
// return new ODBCCachedResultSet($conn, $odbcresult, $fetchmode, true);
return new ODBCResultSet($conn, $odbcresult, $fetchmode);
}
}
?>

View File

@ -0,0 +1,115 @@
<?php
/*
* $Id: ODBCAdapter.php,v 1.3 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>.
*/
/**
* Default class for ODBC driver-specific behavior.
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.3 $
* @package creole.drivers.odbc
*/
class ODBCAdapter
{
/**
* Returns true if column case is preserved in the database when a table
* is first created. Returns false if table does not preserve case (i.e.
* ProductID => PRODUCTID).
*
* @return boolean
*/
public function preservesColumnCase()
{
return true;
}
/**
* Returns true if prepared statements should be emulated. This
* might be useful if your driver does not support (or has trouble with)
* prepared statements.
*
* @return boolean
*/
public function emulatePrepareStmt()
{
return false;
}
/**
* Returns true if ODBC driver supports LIMIT/OFFSET via SQL.
*
* @return boolean
*/
public function hasLimitOffset()
{
return false;
}
/**
* @see Connection::applyLimit()
*/
public function applyLimit(&$sql, $offset, $limit)
{
}
/**
* @see PreparedStatementCommon::escape()
*/
public function escape($str)
{
// use this instead of magic_quotes_sybase + addslashes(),
// just in case multiple RDBMS being used at the same time
return str_replace("'", "''", $str);
}
/**
* Returns an instance of the default resultset.
*
* @return boolean
*/
public function createResultSet($conn, $odbcresult, $fetchmode)
{
require_once 'creole/drivers/odbc/ODBCResultSet.php';
return new ODBCResultSet($conn, $odbcresult, $fetchmode);
}
/**
* Returns the default ODBCIdGenerator for emulating sequences.
*
* @return ODBCIdGenerator
*/
public function getIdGenerator($conn)
{
require_once 'creole/drivers/odbc/ODBCIdGenerator.php';
return new ODBCIdGenerator($conn);
}
/**
* Returns true if driver support transactions.
*
* @return boolean
*/
public function supportsTransactions()
{
return true;
}
}
?>

View File

@ -0,0 +1,66 @@
<?php
/*
* $Id: ODBCDatabaseInfo.php,v 1.2 2006/01/17 19:44:39 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>.
*/
require_once 'creole/metadata/DatabaseInfo.php';
/**
* ODBC implementation of DatabaseInfo.
*
* @todo Still need a way to obtain the database name. Not sure how to do this yet.
* @todo This might need to be an {@link ODBCAdapter} method.
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.2 $
* @package creole.drivers.odbc.metadata
*/
class ODBCDatabaseInfo extends DatabaseInfo {
/**
* @see DatabaseInfo::initTables()
*/
protected function initTables()
{
include_once 'creole/drivers/odbc/metadata/ODBCTableInfo.php';
$result = @odbc_tables($this->conn->getResource());
if (!$result)
throw new SQLException('Could not list tables', $this->conn->nativeError());
while (odbc_fetch_row($result))
{
$tablename = strtoupper(odbc_result($result, 'TABLE_NAME'));
$this->tables[$tablename] = new ODBCTableInfo($this, $tablename);
}
@odbc_free_result($result);
}
/**
* @return void
* @throws SQLException
*/
protected function initSequences()
{
// Not sure how this is used yet.
}
}

View File

@ -0,0 +1,141 @@
<?php
/*
* $Id: ODBCTableInfo.php,v 1.2 2006/01/17 19:44:39 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>.
*/
require_once 'creole/metadata/TableInfo.php';
/**
* ODBC implementation of TableInfo.
*
* @author Dave Lawson <dlawson@masterytech.com>
* @version $Revision: 1.2 $
* @package creole.drivers.odbc.metadata
*/
class ODBCTableInfo extends TableInfo {
/**
* @see TableInfo::initColumns()
*/
protected function initColumns()
{
include_once 'creole/metadata/ColumnInfo.php';
include_once 'creole/drivers/odbc/ODBCTypes.php';
ODBCTypes::loadTypeMap($this->conn);
$result = @odbc_columns($this->conn->getResource(), $this->dbname, '', $this->name);
if (!$result)
throw new SQLException('Could not get column names', $this->conn->nativeError());
while (odbc_fetch_row($result))
{
$name = odbc_result($result, 'COLUMN_NAME');
$type = odbc_result($result, 'TYPE_NAME');
$length = odbc_result($result, 'LENGTH');
$is_nullable = odbc_result($result, 'NULLABLE');
$default = '';
$precision = odbc_result($result, 'PRECISION');
$scale = odbc_result($result, 'SCALE');
$this->columns[$name] = new ColumnInfo($this, $name, ODBCTypes::getType($type), $type, $length, $precision, $scale, $is_nullable, $default);
}
@odbc_free_result($result);
$this->colsLoaded = true;
}
/**
* @see TableInfo::initPrimaryKey()
*/
protected function initPrimaryKey()
{
include_once 'creole/metadata/PrimaryKeyInfo.php';
// columns have to be loaded first
if (!$this->colsLoaded) $this->initColumns();
$result = @odbc_primarykeys($this->conn->getResource(), $this->dbname, '', $this->name);
while (odbc_fetch_row($result))
{
$name = odbc_result($result, 'COLUMN_NAME');
if (!isset($this->primaryKey))
$this->primaryKey = new PrimaryKeyInfo($name);
$this->primaryKey->addColumn($this->columns[$name]);
}
@odbc_free_result($result);
$this->pkLoaded = true;
}
/**
* @see TableInfo::initIndexes()
*/
protected function initIndexes()
{
// Not sure if this can be implemented in a driver-independent way.
}
/**
* @see TableInfo::initForeignKeys()
*/
protected function initForeignKeys()
{
// columns have to be loaded first
if (!$this->colsLoaded) $this->initColumns();
$result = @odbc_foreignkeys($this->conn->getResource(), '', '', '', $this->dbname, '', $this->name);
while (odbc_fetch_row($result))
{
$name = odbc_result($result, 'COLUMN_NAME');
$ftbl = odbc_result($result, 'FKTABLE_NAME');
$fcol = odbc_result($result, 'FKCOLUMN_NAME');
if (!isset($this->foreignKeys[$name]))
{
$this->foreignKeys[$name] = new ForeignKeyInfo($name);
if (($foreignTable = $this->database->getTable($ftbl)) === null)
{
$foreignTable = new TableInfo($ltbl);
$this->database->addTable($foreignTable);
}
if (($foreignCol = $foreignTable->getColumn($name)) === null)
{
$foreignCol = new ColumnInfo($foreignTable, $name);
$foreignTable->addColumn($foreignCol);
}
$this->foreignKeys[$name]->addReference($this->columns[$name], $foreignCol);
}
}
@odbc_free_result($result);
$this->fksLoaded = true;
}
}