2014-12-24 03:28:26 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace PicoDb;
|
|
|
|
|
|
|
|
use Closure;
|
|
|
|
use PDOException;
|
|
|
|
use LogicException;
|
2015-08-15 03:33:39 +02:00
|
|
|
use PicoDb\Driver\Sqlite;
|
|
|
|
use PicoDb\Driver\Mysql;
|
|
|
|
use PicoDb\Driver\Postgres;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Database
|
|
|
|
*
|
|
|
|
* @author Frederic Guillot
|
|
|
|
*/
|
2014-12-24 03:28:26 +01:00
|
|
|
class Database
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Database instances
|
|
|
|
*
|
|
|
|
* @static
|
2015-08-15 03:33:39 +02:00
|
|
|
* @access private
|
2014-12-24 03:28:26 +01:00
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
private static $instances = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Queries logs
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
private $logs = array();
|
|
|
|
|
|
|
|
/**
|
2015-08-15 03:33:39 +02:00
|
|
|
* Driver instance
|
2014-12-24 03:28:26 +01:00
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
*/
|
2015-08-15 03:33:39 +02:00
|
|
|
private $driver;
|
2014-12-24 03:28:26 +01:00
|
|
|
|
2015-01-02 17:54:40 +01:00
|
|
|
/**
|
|
|
|
* Flag to calculate query time
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
public $stopwatch = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Flag to log generated SQL queries
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @var boolean
|
|
|
|
*/
|
2015-08-15 03:33:39 +02:00
|
|
|
public $logQueries = false;
|
2015-01-02 17:54:40 +01:00
|
|
|
|
2015-01-07 01:08:10 +01:00
|
|
|
/**
|
|
|
|
* Number of SQL queries executed
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @var integer
|
|
|
|
*/
|
2015-08-15 03:33:39 +02:00
|
|
|
public $nbQueries = 0;
|
2015-01-07 01:08:10 +01:00
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
/**
|
2015-08-15 03:33:39 +02:00
|
|
|
* Initialize the driver
|
2014-12-24 03:28:26 +01:00
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param array $settings Connection settings
|
|
|
|
*/
|
|
|
|
public function __construct(array $settings)
|
|
|
|
{
|
|
|
|
if (! isset($settings['driver'])) {
|
2015-08-15 03:33:39 +02:00
|
|
|
throw new LogicException('You must define a database driver');
|
2014-12-24 03:28:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
switch ($settings['driver']) {
|
|
|
|
case 'sqlite':
|
2015-08-15 03:33:39 +02:00
|
|
|
$this->driver = new Sqlite($settings);
|
2014-12-24 03:28:26 +01:00
|
|
|
break;
|
|
|
|
case 'mysql':
|
2015-08-15 03:33:39 +02:00
|
|
|
$this->driver = new Mysql($settings);
|
2014-12-24 03:28:26 +01:00
|
|
|
break;
|
|
|
|
case 'postgres':
|
2015-08-15 03:33:39 +02:00
|
|
|
$this->driver = new Postgres($settings);
|
2014-12-24 03:28:26 +01:00
|
|
|
break;
|
|
|
|
default:
|
2015-08-15 03:33:39 +02:00
|
|
|
throw new LogicException('This database driver is not supported');
|
2014-12-24 03:28:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destructor
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
public function __destruct()
|
|
|
|
{
|
|
|
|
$this->closeConnection();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a new database instance
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @access public
|
|
|
|
* @param string $name Instance name
|
|
|
|
* @param Closure $callback Callback
|
|
|
|
*/
|
2015-08-15 03:33:39 +02:00
|
|
|
public static function setInstance($name, Closure $callback)
|
2014-12-24 03:28:26 +01:00
|
|
|
{
|
|
|
|
self::$instances[$name] = $callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a database instance
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @access public
|
|
|
|
* @param string $name Instance name
|
|
|
|
* @return Database
|
|
|
|
*/
|
2015-08-15 03:33:39 +02:00
|
|
|
public static function getInstance($name)
|
2014-12-24 03:28:26 +01:00
|
|
|
{
|
|
|
|
if (! isset(self::$instances[$name])) {
|
2015-08-15 03:33:39 +02:00
|
|
|
throw new LogicException('No database instance created with that name');
|
2014-12-24 03:28:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (is_callable(self::$instances[$name])) {
|
|
|
|
self::$instances[$name] = call_user_func(self::$instances[$name]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return self::$instances[$name];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a log message
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param string $message Message
|
|
|
|
*/
|
|
|
|
public function setLogMessage($message)
|
|
|
|
{
|
|
|
|
$this->logs[] = $message;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all queries logs
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getLogMessages()
|
|
|
|
{
|
|
|
|
return $this->logs;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the PDO connection
|
|
|
|
*
|
|
|
|
* @access public
|
2015-08-15 03:33:39 +02:00
|
|
|
* @return \PDO
|
2014-12-24 03:28:26 +01:00
|
|
|
*/
|
|
|
|
public function getConnection()
|
|
|
|
{
|
2015-08-15 03:33:39 +02:00
|
|
|
return $this->driver->getConnection();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the Driver instance
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @return Sqlite|Postgres|Mysql
|
|
|
|
*/
|
|
|
|
public function getDriver()
|
|
|
|
{
|
|
|
|
return $this->driver;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the last inserted id
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @return integer
|
|
|
|
*/
|
|
|
|
public function getLastId()
|
|
|
|
{
|
|
|
|
return $this->driver->getLastId();
|
2014-12-24 03:28:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Release the PDO connection
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
public function closeConnection()
|
|
|
|
{
|
2015-08-15 03:33:39 +02:00
|
|
|
$this->driver->closeConnection();
|
2014-12-24 03:28:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Escape an identifier (column, table name...)
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param string $value Value
|
2015-01-28 02:13:16 +01:00
|
|
|
* @param string $table Table name
|
2014-12-24 03:28:26 +01:00
|
|
|
* @return string
|
|
|
|
*/
|
2015-01-28 02:13:16 +01:00
|
|
|
public function escapeIdentifier($value, $table = '')
|
2014-12-24 03:28:26 +01:00
|
|
|
{
|
|
|
|
// Do not escape custom query
|
|
|
|
if (strpos($value, '.') !== false || strpos($value, ' ') !== false) {
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
2015-01-28 02:13:16 +01:00
|
|
|
if (! empty($table)) {
|
2015-08-15 03:33:39 +02:00
|
|
|
return $this->driver->escape($table).'.'.$this->driver->escape($value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->driver->escape($value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Escape an identifier list
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param array $identifiers List of identifiers
|
|
|
|
* @param string $table Table name
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
public function escapeIdentifierList(array $identifiers, $table = '')
|
|
|
|
{
|
|
|
|
foreach ($identifiers as $key => $value) {
|
|
|
|
$identifiers[$key] = $this->escapeIdentifier($value, $table);
|
2015-01-28 02:13:16 +01:00
|
|
|
}
|
|
|
|
|
2015-08-15 03:33:39 +02:00
|
|
|
return $identifiers;
|
2014-12-24 03:28:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute a prepared statement
|
|
|
|
*
|
2015-08-15 03:33:39 +02:00
|
|
|
* Note: returns false on duplicate keys instead of SQLException
|
|
|
|
*
|
2014-12-24 03:28:26 +01:00
|
|
|
* @access public
|
|
|
|
* @param string $sql SQL query
|
|
|
|
* @param array $values Values
|
2015-08-15 03:33:39 +02:00
|
|
|
* @return \PDOStatement|false
|
2014-12-24 03:28:26 +01:00
|
|
|
*/
|
|
|
|
public function execute($sql, array $values = array())
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
|
2015-08-15 03:33:39 +02:00
|
|
|
if ($this->logQueries) {
|
2015-01-02 17:54:40 +01:00
|
|
|
$this->setLogMessage($sql);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->stopwatch) {
|
|
|
|
$start = microtime(true);
|
|
|
|
}
|
|
|
|
|
2015-08-15 03:33:39 +02:00
|
|
|
$rq = $this->getConnection()->prepare($sql);
|
2014-12-24 03:28:26 +01:00
|
|
|
$rq->execute($values);
|
2015-01-02 17:54:40 +01:00
|
|
|
|
|
|
|
if ($this->stopwatch) {
|
|
|
|
$this->setLogMessage('DURATION='.(microtime(true) - $start));
|
|
|
|
}
|
|
|
|
|
2015-08-15 03:33:39 +02:00
|
|
|
$this->nbQueries++;
|
2015-01-07 01:08:10 +01:00
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
return $rq;
|
|
|
|
}
|
|
|
|
catch (PDOException $e) {
|
2015-08-15 03:33:39 +02:00
|
|
|
return $this->handleSqlError($e);
|
2014-12-24 03:28:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Run a transaction
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param Closure $callback Callback
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function transaction(Closure $callback)
|
|
|
|
{
|
2015-08-15 03:33:39 +02:00
|
|
|
try {
|
|
|
|
|
|
|
|
$this->startTransaction();
|
|
|
|
$result = $callback($this);
|
|
|
|
$this->closeTransaction();
|
|
|
|
|
|
|
|
return $result === null ? true : $result;
|
|
|
|
}
|
|
|
|
catch (PDOException $e) {
|
|
|
|
return $this->handleSqlError($e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle PDOException
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @param PDOException $e
|
|
|
|
* @return bool
|
|
|
|
* @throws SQLException
|
|
|
|
*/
|
|
|
|
private function handleSqlError(PDOException $e)
|
|
|
|
{
|
|
|
|
$this->cancelTransaction();
|
|
|
|
$this->setLogMessage($e->getMessage());
|
|
|
|
|
|
|
|
if ($this->driver->isDuplicateKeyError($e->getCode())) {
|
|
|
|
return false;
|
|
|
|
}
|
2014-12-24 03:28:26 +01:00
|
|
|
|
2015-08-15 03:33:39 +02:00
|
|
|
throw new SQLException('SQL error'.($this->logQueries ? ': '.$e->getMessage() : ''));
|
2014-12-24 03:28:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Begin a transaction
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
public function startTransaction()
|
|
|
|
{
|
2015-08-15 03:33:39 +02:00
|
|
|
if (! $this->getConnection()->inTransaction()) {
|
|
|
|
$this->getConnection()->beginTransaction();
|
2014-12-24 03:28:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Commit a transaction
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
public function closeTransaction()
|
|
|
|
{
|
2015-08-15 03:33:39 +02:00
|
|
|
if ($this->getConnection()->inTransaction()) {
|
|
|
|
$this->getConnection()->commit();
|
2014-12-24 03:28:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Rollback a transaction
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
public function cancelTransaction()
|
|
|
|
{
|
2015-08-15 03:33:39 +02:00
|
|
|
if ($this->getConnection()->inTransaction()) {
|
|
|
|
$this->getConnection()->rollback();
|
2014-12-24 03:28:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a table instance
|
|
|
|
*
|
|
|
|
* @access public
|
2015-08-15 03:33:39 +02:00
|
|
|
* @param string $table_name
|
|
|
|
* @return Table
|
2014-12-24 03:28:26 +01:00
|
|
|
*/
|
|
|
|
public function table($table_name)
|
|
|
|
{
|
|
|
|
return new Table($this, $table_name);
|
|
|
|
}
|
|
|
|
|
2015-01-28 02:13:16 +01:00
|
|
|
/**
|
|
|
|
* Get a hashtable instance
|
|
|
|
*
|
|
|
|
* @access public
|
2015-08-15 03:33:39 +02:00
|
|
|
* @param string $table_name
|
|
|
|
* @return Hashtable
|
2015-01-28 02:13:16 +01:00
|
|
|
*/
|
|
|
|
public function hashtable($table_name)
|
|
|
|
{
|
|
|
|
return new Hashtable($this, $table_name);
|
|
|
|
}
|
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
/**
|
|
|
|
* Get a schema instance
|
|
|
|
*
|
|
|
|
* @access public
|
2015-08-15 03:33:39 +02:00
|
|
|
* @return Schema
|
2014-12-24 03:28:26 +01:00
|
|
|
*/
|
|
|
|
public function schema()
|
|
|
|
{
|
|
|
|
return new Schema($this);
|
|
|
|
}
|
|
|
|
}
|