mirror of
https://github.com/dg/dibi.git
synced 2025-02-24 10:53:17 +01:00
502 lines
12 KiB
PHP
502 lines
12 KiB
PHP
<?php
|
|
|
|
/**
|
|
* This file is part of the "dibi" - smart database abstraction layer.
|
|
* Copyright (c) 2005 David Grudl (http://davidgrudl.com)
|
|
*/
|
|
|
|
|
|
require_once dirname(__FILE__) . '/DibiMySqlReflector.php';
|
|
require_once dirname(__FILE__) . '/DibiSqliteReflector.php';
|
|
|
|
|
|
/**
|
|
* The dibi driver for PDO.
|
|
*
|
|
* Driver options:
|
|
* - dsn => driver specific DSN
|
|
* - username (or user)
|
|
* - password (or pass)
|
|
* - options (array) => driver specific options {@see PDO::__construct}
|
|
* - resource (PDO) => existing connection
|
|
* - lazy, profiler, result, substitutes, ... => see DibiConnection options
|
|
*
|
|
* @author David Grudl
|
|
* @package dibi\drivers
|
|
*/
|
|
class DibiPdoDriver extends DibiObject implements IDibiDriver, IDibiResultDriver
|
|
{
|
|
/** @var PDO Connection resource */
|
|
private $connection;
|
|
|
|
/** @var PDOStatement Resultset resource */
|
|
private $resultSet;
|
|
|
|
/** @var int|FALSE Affected rows */
|
|
private $affectedRows = FALSE;
|
|
|
|
/** @var string */
|
|
private $driverName;
|
|
|
|
|
|
/**
|
|
* @throws DibiNotSupportedException
|
|
*/
|
|
public function __construct()
|
|
{
|
|
if (!extension_loaded('pdo')) {
|
|
throw new DibiNotSupportedException("PHP extension 'pdo' is not loaded.");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Connects to a database.
|
|
* @return void
|
|
* @throws DibiException
|
|
*/
|
|
public function connect(array & $config)
|
|
{
|
|
$foo = & $config['dsn'];
|
|
$foo = & $config['options'];
|
|
DibiConnection::alias($config, 'resource', 'pdo');
|
|
|
|
if ($config['resource'] instanceof PDO) {
|
|
$this->connection = $config['resource'];
|
|
|
|
} else try {
|
|
$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
|
|
|
|
} catch (PDOException $e) {
|
|
if ($e->getMessage() === 'could not find driver') {
|
|
throw new DibiNotSupportedException("PHP extension for PDO is not loaded.");
|
|
}
|
|
throw new DibiDriverException($e->getMessage(), $e->getCode());
|
|
}
|
|
|
|
$this->driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
|
|
}
|
|
|
|
|
|
/**
|
|
* Disconnects from a database.
|
|
* @return void
|
|
*/
|
|
public function disconnect()
|
|
{
|
|
$this->connection = NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Executes the SQL query.
|
|
* @param string SQL statement.
|
|
* @return IDibiResultDriver|NULL
|
|
* @throws DibiDriverException
|
|
*/
|
|
public function query($sql)
|
|
{
|
|
// must detect if SQL returns result set or num of affected rows
|
|
$cmd = strtoupper(substr(ltrim($sql), 0, 6));
|
|
static $list = array('UPDATE'=>1, 'DELETE'=>1, 'INSERT'=>1, 'REPLAC'=>1);
|
|
$this->affectedRows = FALSE;
|
|
|
|
if (isset($list[$cmd])) {
|
|
$this->affectedRows = $this->connection->exec($sql);
|
|
|
|
if ($this->affectedRows === FALSE) {
|
|
$err = $this->connection->errorInfo();
|
|
throw new DibiDriverException("SQLSTATE[$err[0]]: $err[2]", $err[1], $sql);
|
|
}
|
|
|
|
} else {
|
|
$res = $this->connection->query($sql);
|
|
|
|
if ($res === FALSE) {
|
|
$err = $this->connection->errorInfo();
|
|
throw new DibiDriverException("SQLSTATE[$err[0]]: $err[2]", $err[1], $sql);
|
|
} else {
|
|
return $this->createResultDriver($res);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
|
|
* @return int|FALSE number of rows or FALSE on error
|
|
*/
|
|
public function getAffectedRows()
|
|
{
|
|
return $this->affectedRows;
|
|
}
|
|
|
|
|
|
/**
|
|
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
|
|
* @return int|FALSE int on success or FALSE on failure
|
|
*/
|
|
public function getInsertId($sequence)
|
|
{
|
|
return $this->connection->lastInsertId();
|
|
}
|
|
|
|
|
|
/**
|
|
* Begins a transaction (if supported).
|
|
* @param string optional savepoint name
|
|
* @return void
|
|
* @throws DibiDriverException
|
|
*/
|
|
public function begin($savepoint = NULL)
|
|
{
|
|
if (!$this->connection->beginTransaction()) {
|
|
$err = $this->connection->errorInfo();
|
|
throw new DibiDriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Commits statements in a transaction.
|
|
* @param string optional savepoint name
|
|
* @return void
|
|
* @throws DibiDriverException
|
|
*/
|
|
public function commit($savepoint = NULL)
|
|
{
|
|
if (!$this->connection->commit()) {
|
|
$err = $this->connection->errorInfo();
|
|
throw new DibiDriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Rollback changes in a transaction.
|
|
* @param string optional savepoint name
|
|
* @return void
|
|
* @throws DibiDriverException
|
|
*/
|
|
public function rollback($savepoint = NULL)
|
|
{
|
|
if (!$this->connection->rollBack()) {
|
|
$err = $this->connection->errorInfo();
|
|
throw new DibiDriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the connection resource.
|
|
* @return PDO
|
|
*/
|
|
public function getResource()
|
|
{
|
|
return $this->connection;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the connection reflector.
|
|
* @return IDibiReflector
|
|
*/
|
|
public function getReflector()
|
|
{
|
|
switch ($this->driverName) {
|
|
case 'mysql':
|
|
return new DibiMySqlReflector($this);
|
|
|
|
case 'sqlite':
|
|
case 'sqlite2':
|
|
return new DibiSqliteReflector($this);
|
|
|
|
default:
|
|
throw new DibiNotSupportedException;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Result set driver factory.
|
|
* @param PDOStatement
|
|
* @return IDibiResultDriver
|
|
*/
|
|
public function createResultDriver(PDOStatement $resource)
|
|
{
|
|
$res = clone $this;
|
|
$res->resultSet = $resource;
|
|
return $res;
|
|
}
|
|
|
|
|
|
/********************* SQL ****************d*g**/
|
|
|
|
|
|
/**
|
|
* Encodes data for use in a SQL statement.
|
|
* @param mixed value
|
|
* @param string type (dibi::TEXT, dibi::BOOL, ...)
|
|
* @return string encoded value
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public function escape($value, $type)
|
|
{
|
|
switch ($type) {
|
|
case dibi::TEXT:
|
|
case dibi::BINARY:
|
|
if ($this->driverName === 'odbc') {
|
|
return "'" . str_replace("'", "''", $value) . "'";
|
|
} else {
|
|
return $this->connection->quote($value, $type === dibi::TEXT ? PDO::PARAM_STR : PDO::PARAM_LOB);
|
|
}
|
|
|
|
case dibi::IDENTIFIER:
|
|
switch ($this->driverName) {
|
|
case 'mysql':
|
|
return '`' . str_replace('`', '``', $value) . '`';
|
|
|
|
case 'oci':
|
|
case 'pgsql':
|
|
return '"' . str_replace('"', '""', $value) . '"';
|
|
|
|
case 'sqlite':
|
|
case 'sqlite2':
|
|
return '[' . strtr($value, '[]', ' ') . ']';
|
|
|
|
case 'odbc':
|
|
case 'mssql':
|
|
return '[' . str_replace(array('[', ']'), array('[[', ']]'), $value) . ']';
|
|
|
|
case 'sqlsrv':
|
|
return '[' . str_replace(']', ']]', $value) . ']';
|
|
|
|
default:
|
|
return $value;
|
|
}
|
|
|
|
case dibi::BOOL:
|
|
if ($this->driverName === 'pgsql') {
|
|
return $value ? 'TRUE' : 'FALSE';
|
|
} else {
|
|
return $value ? 1 : 0;
|
|
}
|
|
|
|
case dibi::DATE:
|
|
case dibi::DATETIME:
|
|
if (!$value instanceof DateTime && !$value instanceof DateTimeInterface) {
|
|
$value = new DibiDateTime($value);
|
|
}
|
|
if ($this->driverName === 'odbc') {
|
|
return $value->format($type === dibi::DATETIME ? "#m/d/Y H:i:s#" : "#m/d/Y#");
|
|
} else {
|
|
return $value->format($type === dibi::DATETIME ? "'Y-m-d H:i:s'" : "'Y-m-d'");
|
|
}
|
|
|
|
default:
|
|
throw new InvalidArgumentException('Unsupported type.');
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Encodes string for use in a LIKE statement.
|
|
* @param string
|
|
* @param int
|
|
* @return string
|
|
*/
|
|
public function escapeLike($value, $pos)
|
|
{
|
|
switch ($this->driverName) {
|
|
case 'mysql':
|
|
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
|
|
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
|
|
|
case 'oci':
|
|
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
|
|
$value = str_replace("'", "''", $value);
|
|
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
|
|
|
case 'pgsql':
|
|
$value = substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1);
|
|
$value = strtr($value, array( '%' => '\\\\%', '_' => '\\\\_'));
|
|
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
|
|
|
case 'sqlite':
|
|
case 'sqlite2':
|
|
$value = addcslashes(substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1), '%_\\');
|
|
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
|
|
|
|
case 'odbc':
|
|
case 'mssql':
|
|
case 'sqlsrv':
|
|
$value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]'));
|
|
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
|
|
|
|
default:
|
|
throw new DibiNotImplementedException;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Decodes data from result set.
|
|
* @param string value
|
|
* @param string type (dibi::BINARY)
|
|
* @return string decoded value
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public function unescape($value, $type)
|
|
{
|
|
if ($type === dibi::BINARY) {
|
|
return $value;
|
|
}
|
|
throw new InvalidArgumentException('Unsupported type.');
|
|
}
|
|
|
|
|
|
/**
|
|
* Injects LIMIT/OFFSET to the SQL query.
|
|
* @return void
|
|
*/
|
|
public function applyLimit(& $sql, $limit, $offset)
|
|
{
|
|
if ($limit < 0 && $offset < 1) {
|
|
return;
|
|
}
|
|
|
|
switch ($this->driverName) {
|
|
case 'mysql':
|
|
$sql .= ' LIMIT ' . ($limit < 0 ? '18446744073709551615' : (int) $limit)
|
|
. ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
|
|
break;
|
|
|
|
case 'pgsql':
|
|
if ($limit >= 0) {
|
|
$sql .= ' LIMIT ' . (int) $limit;
|
|
}
|
|
if ($offset > 0) {
|
|
$sql .= ' OFFSET ' . (int) $offset;
|
|
}
|
|
break;
|
|
|
|
case 'sqlite':
|
|
case 'sqlite2':
|
|
$sql .= ' LIMIT ' . $limit . ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
|
|
break;
|
|
|
|
case 'oci':
|
|
if ($offset > 0) {
|
|
$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
|
|
. ($limit >= 0 ? 'WHERE ROWNUM <= ' . ((int) $offset + (int) $limit) : '')
|
|
. ') WHERE "__rnum" > '. (int) $offset;
|
|
} elseif ($limit >= 0) {
|
|
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . (int) $limit;
|
|
}
|
|
break;
|
|
|
|
case 'odbc':
|
|
case 'dblib':
|
|
case 'mssql':
|
|
case 'sqlsrv':
|
|
if ($offset < 1) {
|
|
$sql = 'SELECT TOP ' . (int) $limit . ' * FROM (' . $sql . ') t';
|
|
break;
|
|
}
|
|
// intentionally break omitted
|
|
|
|
default:
|
|
throw new DibiNotSupportedException('PDO or driver does not support applying limit or offset.');
|
|
}
|
|
}
|
|
|
|
|
|
/********************* result set ****************d*g**/
|
|
|
|
|
|
/**
|
|
* Returns the number of rows in a result set.
|
|
* @return int
|
|
*/
|
|
public function getRowCount()
|
|
{
|
|
return $this->resultSet->rowCount();
|
|
}
|
|
|
|
|
|
/**
|
|
* Fetches the row at current position and moves the internal cursor to the next position.
|
|
* @param bool TRUE for associative array, FALSE for numeric
|
|
* @return array array on success, nonarray if no next record
|
|
*/
|
|
public function fetch($assoc)
|
|
{
|
|
return $this->resultSet->fetch($assoc ? PDO::FETCH_ASSOC : PDO::FETCH_NUM);
|
|
}
|
|
|
|
|
|
/**
|
|
* Moves cursor position without fetching row.
|
|
* @param int the 0-based cursor pos to seek to
|
|
* @return boolean TRUE on success, FALSE if unable to seek to specified record
|
|
*/
|
|
public function seek($row)
|
|
{
|
|
throw new DibiNotSupportedException('Cannot seek an unbuffered result set.');
|
|
}
|
|
|
|
|
|
/**
|
|
* Frees the resources allocated for this result set.
|
|
* @return void
|
|
*/
|
|
public function free()
|
|
{
|
|
$this->resultSet = NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns metadata for all columns in a result set.
|
|
* @return array
|
|
* @throws DibiException
|
|
*/
|
|
public function getResultColumns()
|
|
{
|
|
$count = $this->resultSet->columnCount();
|
|
$columns = array();
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$row = @$this->resultSet->getColumnMeta($i); // intentionally @
|
|
if ($row === FALSE) {
|
|
throw new DibiNotSupportedException('Driver does not support meta data.');
|
|
}
|
|
// PHP < 5.2.3 compatibility
|
|
// @see: http://php.net/manual/en/pdostatement.getcolumnmeta.php#pdostatement.getcolumnmeta.changelog
|
|
$row = $row + array(
|
|
'table' => NULL,
|
|
'native_type' => 'VAR_STRING',
|
|
);
|
|
|
|
$columns[] = array(
|
|
'name' => $row['name'],
|
|
'table' => $row['table'],
|
|
'nativetype' => $row['native_type'],
|
|
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
|
|
'vendor' => $row,
|
|
);
|
|
}
|
|
return $columns;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the result set resource.
|
|
* @return PDOStatement
|
|
*/
|
|
public function getResultResource()
|
|
{
|
|
return $this->resultSet;
|
|
}
|
|
|
|
}
|