Update PicoDb version

This commit is contained in:
Frederic Guillot 2016-07-30 18:41:42 -04:00
parent 920e522c9e
commit d3fb2bf69d
No known key found for this signature in database
GPG Key ID: 92D77191BA7FBC99
26 changed files with 1679 additions and 299 deletions

View File

@ -61,17 +61,13 @@ PicoDb\Database::setInstance('db', function() {
else {
$errors = $db->getLogMessages();
$pdo = new \PDO('sqlite::memory:');
$result = $pdo->query('select sqlite_version()', PDO::FETCH_COLUMN, 0);
$sqlite_version = $result ? $result->fetch() : '?';
$html = 'Unable to migrate the database schema, <strong>please copy and paste this message and create a bug report:</strong><hr/>';
$html .= '<pre><code>';
$html .= (isset($errors[0]) ? $errors[0] : 'Unknown SQL error').PHP_EOL.PHP_EOL;
$html .= '- PHP version: '.phpversion().PHP_EOL;
$html .= '- SAPI: '.php_sapi_name().PHP_EOL;
$html .= '- PDO Sqlite version: '.phpversion('pdo_sqlite').PHP_EOL;
$html .= '- Sqlite version: '.$sqlite_version.PHP_EOL;
$html .= '- Sqlite version: '.$db->getDriver()->getDatabaseVersion().PHP_EOL;
$html .= '- OS: '.php_uname();
$html .= '</code></pre>';

View File

@ -14,7 +14,7 @@
"require": {
"fguillot/simple-validator": "v1.0.0",
"fguillot/json-rpc": "v1.1.0",
"fguillot/picodb": "v1.0.2",
"fguillot/picodb": "v1.0.14 ",
"fguillot/picofeed": "v0.1.24",
"pda/pheanstalk": "v3.1.0"
},

View File

@ -546,7 +546,7 @@ function cleanup($feed_id, array $items_in_feed)
$removed_items = $db
->table('items')
->columns('id')
->notin('id', $items_in_feed)
->notIn('id', $items_in_feed)
->eq('status', 'removed')
->eq('feed_id', $feed_id)
->desc('updated')

View File

@ -14,6 +14,8 @@ const EXPIRATION = 5184000;
* Get a remember me record
*
* @access public
* @param string $token
* @param string $sequence
* @return mixed
*/
function find($token, $sequence)

View File

@ -72,16 +72,25 @@ return array(
'Pheanstalk\\Socket\\StreamFunctions' => $vendorDir . '/pda/pheanstalk/src/Socket/StreamFunctions.php',
'Pheanstalk\\Socket\\WriteHistory' => $vendorDir . '/pda/pheanstalk/src/Socket/WriteHistory.php',
'Pheanstalk\\YamlResponseParser' => $vendorDir . '/pda/pheanstalk/src/YamlResponseParser.php',
'PicoDb\\Condition' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Condition.php',
'PicoDb\\Builder\\BaseBuilder' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Builder/BaseBuilder.php',
'PicoDb\\Builder\\ConditionBuilder' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Builder/ConditionBuilder.php',
'PicoDb\\Builder\\InsertBuilder' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Builder/InsertBuilder.php',
'PicoDb\\Builder\\OrConditionBuilder' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Builder/OrConditionBuilder.php',
'PicoDb\\Builder\\UpdateBuilder' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Builder/UpdateBuilder.php',
'PicoDb\\Database' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Database.php',
'PicoDb\\DriverFactory' => $vendorDir . '/fguillot/picodb/lib/PicoDb/DriverFactory.php',
'PicoDb\\Driver\\Base' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Driver/Base.php',
'PicoDb\\Driver\\Mssql' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Driver/Mssql.php',
'PicoDb\\Driver\\Mysql' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Driver/Mysql.php',
'PicoDb\\Driver\\Postgres' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Driver/Postgres.php',
'PicoDb\\Driver\\Sqlite' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Driver/Sqlite.php',
'PicoDb\\Hashtable' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Hashtable.php',
'PicoDb\\LargeObject' => $vendorDir . '/fguillot/picodb/lib/PicoDb/LargeObject.php',
'PicoDb\\SQLException' => $vendorDir . '/fguillot/picodb/lib/PicoDb/SQLException.php',
'PicoDb\\Schema' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Schema.php',
'PicoDb\\StatementHandler' => $vendorDir . '/fguillot/picodb/lib/PicoDb/StatementHandler.php',
'PicoDb\\Table' => $vendorDir . '/fguillot/picodb/lib/PicoDb/Table.php',
'PicoDb\\UrlParser' => $vendorDir . '/fguillot/picodb/lib/PicoDb/UrlParser.php',
'PicoFeed\\Base' => $vendorDir . '/fguillot/picofeed/lib/PicoFeed/Base.php',
'PicoFeed\\Client\\Client' => $vendorDir . '/fguillot/picofeed/lib/PicoFeed/Client/Client.php',
'PicoFeed\\Client\\ClientException' => $vendorDir . '/fguillot/picofeed/lib/PicoFeed/Client/ClientException.php',

View File

@ -144,16 +144,25 @@ class ComposerStaticInitfd7e8d436e1dc450edc3153ac8bc31b4
'Pheanstalk\\Socket\\StreamFunctions' => __DIR__ . '/..' . '/pda/pheanstalk/src/Socket/StreamFunctions.php',
'Pheanstalk\\Socket\\WriteHistory' => __DIR__ . '/..' . '/pda/pheanstalk/src/Socket/WriteHistory.php',
'Pheanstalk\\YamlResponseParser' => __DIR__ . '/..' . '/pda/pheanstalk/src/YamlResponseParser.php',
'PicoDb\\Condition' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Condition.php',
'PicoDb\\Builder\\BaseBuilder' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Builder/BaseBuilder.php',
'PicoDb\\Builder\\ConditionBuilder' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Builder/ConditionBuilder.php',
'PicoDb\\Builder\\InsertBuilder' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Builder/InsertBuilder.php',
'PicoDb\\Builder\\OrConditionBuilder' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Builder/OrConditionBuilder.php',
'PicoDb\\Builder\\UpdateBuilder' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Builder/UpdateBuilder.php',
'PicoDb\\Database' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Database.php',
'PicoDb\\DriverFactory' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/DriverFactory.php',
'PicoDb\\Driver\\Base' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Driver/Base.php',
'PicoDb\\Driver\\Mssql' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Driver/Mssql.php',
'PicoDb\\Driver\\Mysql' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Driver/Mysql.php',
'PicoDb\\Driver\\Postgres' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Driver/Postgres.php',
'PicoDb\\Driver\\Sqlite' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Driver/Sqlite.php',
'PicoDb\\Hashtable' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Hashtable.php',
'PicoDb\\LargeObject' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/LargeObject.php',
'PicoDb\\SQLException' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/SQLException.php',
'PicoDb\\Schema' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Schema.php',
'PicoDb\\StatementHandler' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/StatementHandler.php',
'PicoDb\\Table' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/Table.php',
'PicoDb\\UrlParser' => __DIR__ . '/..' . '/fguillot/picodb/lib/PicoDb/UrlParser.php',
'PicoFeed\\Base' => __DIR__ . '/..' . '/fguillot/picofeed/lib/PicoFeed/Base.php',
'PicoFeed\\Client\\Client' => __DIR__ . '/..' . '/fguillot/picofeed/lib/PicoFeed/Client/Client.php',
'PicoFeed\\Client\\ClientException' => __DIR__ . '/..' . '/fguillot/picofeed/lib/PicoFeed/Client/ClientException.php',

View File

@ -37,45 +37,6 @@
"description": "Simple validator library",
"homepage": "https://github.com/fguillot/simpleValidator"
},
{
"name": "fguillot/picodb",
"version": "v1.0.2",
"version_normalized": "1.0.2.0",
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoDb.git",
"reference": "61f492c125d9195ce869447e2b2450adeb3b01d6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fguillot/picoDb/zipball/61f492c125d9195ce869447e2b2450adeb3b01d6",
"reference": "61f492c125d9195ce869447e2b2450adeb3b01d6",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2015-08-27 23:33:16",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"PicoDb": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frédéric Guillot",
"homepage": "http://fredericguillot.com"
}
],
"description": "Minimalist database query builder",
"homepage": "https://github.com/fguillot/picoDb"
},
{
"name": "zendframework/zendxml",
"version": "1.0.2",
@ -262,5 +223,47 @@
],
"description": "Modern library to handle RSS/Atom feeds",
"homepage": "https://github.com/fguillot/picoFeed"
},
{
"name": "fguillot/picodb",
"version": "v1.0.14",
"version_normalized": "1.0.14.0",
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoDb.git",
"reference": "86a831302ab10af800c83dbe4b3b01c88d5433f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fguillot/picoDb/zipball/86a831302ab10af800c83dbe4b3b01c88d5433f1",
"reference": "86a831302ab10af800c83dbe4b3b01c88d5433f1",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "4.8.*"
},
"time": "2016-07-16 22:59:59",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"PicoDb": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frédéric Guillot",
"homepage": "https://github.com/fguillot/"
}
],
"description": "Minimalist database query builder",
"homepage": "https://github.com/fguillot/picoDb"
}
]

View File

@ -0,0 +1,86 @@
<?php
namespace PicoDb\Builder;
use PicoDb\Database;
/**
* Class InsertBuilder
*
* @package PicoDb\Builder
* @author Frederic Guillot
*/
abstract class BaseBuilder
{
/**
* @var Database
*/
protected $db;
/**
* @var ConditionBuilder
*/
protected $conditionBuilder;
/**
* @var string
*/
protected $table = '';
/**
* @var string[]
*/
protected $columns = array();
/**
* InsertBuilder constructor
*
* @param Database $db
* @param ConditionBuilder $condition
*/
public function __construct(Database $db, ConditionBuilder $condition)
{
$this->db = $db;
$this->conditionBuilder = $condition;
}
/**
* Get object instance
*
* @static
* @access public
* @param Database $db
* @param ConditionBuilder $condition
* @return static
*/
public static function getInstance(Database $db, ConditionBuilder $condition)
{
return new static($db, $condition);
}
/**
* Set table name
*
* @access public
* @param string $table
* @return $this
*/
public function withTable($table)
{
$this->table = $table;
return $this;
}
/**
* Set columns name
*
* @access public
* @param string[] $columns
* @return $this
*/
public function withColumns(array $columns)
{
$this->columns = $columns;
return $this;
}
}

View File

@ -1,13 +1,17 @@
<?php
namespace PicoDb;
namespace PicoDb\Builder;
use PicoDb\Database;
use PicoDb\Table;
/**
* Handle SQL conditions
*
* @author Frederic Guillot
* @package PicoDb\Builder
* @author Frederic Guillot
*/
class Condition
class ConditionBuilder
{
/**
* Database instance
@ -26,10 +30,10 @@ class Condition
private $values = array();
/**
* SQL conditions
* SQL AND conditions
*
* @access private
* @var array
* @var string[]
*/
private $conditions = array();
@ -37,17 +41,17 @@ class Condition
* SQL OR conditions
*
* @access private
* @var array
* @var OrConditionBuilder[]
*/
private $or = array();
private $orConditions = array();
/**
* OR condition started
* SQL condition offset
*
* @access private
* @var boolean
* @var int
*/
private $beginOr = false;
private $orConditionOffset = 0;
/**
* Constructor
@ -101,8 +105,8 @@ class Condition
*/
public function addCondition($sql)
{
if ($this->beginOr) {
$this->or[] = $sql;
if ($this->orConditionOffset > 0) {
$this->orConditions[$this->orConditionOffset]->withCondition($sql);
}
else {
$this->conditions[] = $sql;
@ -116,9 +120,10 @@ class Condition
*/
public function beginOr()
{
$this->beginOr = true;
$this->or = array();
$this->orConditionOffset++;
$this->orConditions[$this->orConditionOffset] = new OrConditionBuilder();
}
/**
* Close OR condition
*
@ -126,10 +131,13 @@ class Condition
*/
public function closeOr()
{
$this->beginOr = false;
$condition = $this->orConditions[$this->orConditionOffset]->build();
$this->orConditionOffset--;
if (! empty($this->or)) {
$this->conditions[] = '('.implode(' OR ', $this->or).')';
if ($this->orConditionOffset > 0) {
$this->orConditions[$this->orConditionOffset]->withCondition($condition);
} else {
$this->conditions[] = $condition;
}
}
@ -174,6 +182,19 @@ class Condition
}
}
/**
* IN condition with a subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function inSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' IN ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* NOT IN condition
*
@ -181,7 +202,7 @@ class Condition
* @param string $column
* @param array $values
*/
public function notin($column, array $values)
public function notIn($column, array $values)
{
if (! empty($values)) {
$this->addCondition($this->db->escapeIdentifier($column).' NOT IN ('.implode(', ', array_fill(0, count($values), '?')).')');
@ -189,6 +210,19 @@ class Condition
}
}
/**
* NOT IN condition with a subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function notInSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' NOT IN ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* LIKE condition
*
@ -228,6 +262,19 @@ class Condition
$this->values[] = $value;
}
/**
* Greater than condition with subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function gtSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' > ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* Lower than condition
*
@ -241,6 +288,19 @@ class Condition
$this->values[] = $value;
}
/**
* Lower than condition with subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function ltSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' < ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* Greater than or equals condition
*
@ -254,6 +314,19 @@ class Condition
$this->values[] = $value;
}
/**
* Greater than or equal condition with subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function gteSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' >= ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* Lower than or equals condition
*
@ -267,6 +340,19 @@ class Condition
$this->values[] = $value;
}
/**
* Lower than or equal condition with subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function lteSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' <= ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* IS NULL condition
*

View File

@ -0,0 +1,36 @@
<?php
namespace PicoDb\Builder;
/**
* Class InsertBuilder
*
* @package PicoDb\Builder
* @author Frederic Guillot
*/
class InsertBuilder extends BaseBuilder
{
/**
* Build SQL
*
* @access public
* @return string
*/
public function build()
{
$columns = array();
$placeholders = array();
foreach ($this->columns as $column) {
$columns[] = $this->db->escapeIdentifier($column);
$placeholders[] = ':'.$column;
}
return sprintf(
'INSERT INTO %s (%s) VALUES (%s)',
$this->db->escapeIdentifier($this->table),
implode(', ', $columns),
implode(', ', $placeholders)
);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace PicoDb\Builder;
/**
* Class OrConditionBuilder
*
* @package PicoDb\Builder
* @author Frederic Guillot
*/
class OrConditionBuilder
{
/**
* List of SQL conditions
*
* @access protected
* @var string[]
*/
protected $conditions = array();
/**
* Add new condition
*
* @access public
* @param string $condition
* @return $this
*/
public function withCondition($condition) {
$this->conditions[] = $condition;
return $this;
}
/**
* Build SQL
*
* @access public
* @return string
*/
public function build()
{
return '('.implode(' OR ', $this->conditions).')';
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace PicoDb\Builder;
/**
* Class UpdateBuilder
*
* @package PicoDb\Builder
* @author Frederic Guillot
*/
class UpdateBuilder extends BaseBuilder
{
/**
* @var string[]
*/
protected $sumColumns = array();
/**
* Set columns name
*
* @access public
* @param string[] $columns
* @return $this
*/
public function withSumColumns(array $columns)
{
$this->sumColumns = $columns;
return $this;
}
/**
* Build SQL
*
* @access public
* @return string
*/
public function build()
{
$columns = array();
foreach ($this->columns as $column) {
$columns[] = $this->db->escapeIdentifier($column).'=?';
}
foreach ($this->sumColumns as $column) {
$columns[] = $this->db->escapeIdentifier($column).'='.$this->db->escapeIdentifier($column).' + ?';
}
return sprintf(
'UPDATE %s SET %s %s',
$this->db->escapeIdentifier($this->table),
implode(', ', $columns),
$this->conditionBuilder->build()
);
}
}

View File

@ -5,6 +5,7 @@ namespace PicoDb;
use Closure;
use PDOException;
use LogicException;
use PicoDb\Driver\Mssql;
use PicoDb\Driver\Sqlite;
use PicoDb\Driver\Mysql;
use PicoDb\Driver\Postgres;
@ -12,7 +13,8 @@ use PicoDb\Driver\Postgres;
/**
* Database
*
* @author Frederic Guillot
* @package PicoDb
* @author Frederic Guillot
*/
class Database
{
@ -25,6 +27,14 @@ class Database
*/
private static $instances = array();
/**
* Statement object
*
* @access protected
* @var StatementHandler
*/
protected $statementHandler;
/**
* Queries logs
*
@ -40,55 +50,16 @@ class Database
*/
private $driver;
/**
* Flag to calculate query time
*
* @access public
* @var boolean
*/
public $stopwatch = false;
/**
* Flag to log generated SQL queries
*
* @access public
* @var boolean
*/
public $logQueries = false;
/**
* Number of SQL queries executed
*
* @access public
* @var integer
*/
public $nbQueries = 0;
/**
* Initialize the driver
*
* @access public
* @param array $settings Connection settings
* @param array $settings
*/
public function __construct(array $settings)
public function __construct(array $settings = array())
{
if (! isset($settings['driver'])) {
throw new LogicException('You must define a database driver');
}
switch ($settings['driver']) {
case 'sqlite':
$this->driver = new Sqlite($settings);
break;
case 'mysql':
$this->driver = new Mysql($settings);
break;
case 'postgres':
$this->driver = new Postgres($settings);
break;
default:
throw new LogicException('This database driver is not supported');
}
$this->driver = DriverFactory::getDriver($settings);
$this->statementHandler = new StatementHandler($this);
}
/**
@ -139,11 +110,29 @@ class Database
* Add a log message
*
* @access public
* @param string $message Message
* @param mixed $message
* @return Database
*/
public function setLogMessage($message)
{
$this->logs[] = $message;
$this->logs[] = is_array($message) ? var_export($message, true) : $message;
return $this;
}
/**
* Add many log messages
*
* @access public
* @param array $messages
* @return Database
*/
public function setLogMessages(array $messages)
{
foreach ($messages as $message) {
$this->setLogMessage($message);
}
return $this;
}
/**
@ -172,7 +161,7 @@ class Database
* Get the Driver instance
*
* @access public
* @return Sqlite|Postgres|Mysql
* @return Mssql|Sqlite|Postgres|Mysql
*/
public function getDriver()
{
@ -187,7 +176,18 @@ class Database
*/
public function getLastId()
{
return $this->driver->getLastId();
return (int) $this->driver->getLastId();
}
/**
* Get statement object
*
* @access public
* @return StatementHandler
*/
public function getStatementHandler()
{
return $this->statementHandler;
}
/**
@ -251,30 +251,10 @@ class Database
*/
public function execute($sql, array $values = array())
{
try {
if ($this->logQueries) {
$this->setLogMessage($sql);
}
if ($this->stopwatch) {
$start = microtime(true);
}
$rq = $this->getConnection()->prepare($sql);
$rq->execute($values);
if ($this->stopwatch) {
$this->setLogMessage('DURATION='.(microtime(true) - $start));
}
$this->nbQueries++;
return $rq;
}
catch (PDOException $e) {
return $this->handleSqlError($e);
}
return $this->statementHandler
->withSql($sql)
->withPositionalParams($values)
->execute();
}
/**
@ -293,30 +273,9 @@ class Database
$this->closeTransaction();
return $result === null ? true : $result;
} catch (PDOException $e) {
return $this->statementHandler->handleSqlError($e);
}
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;
}
throw new SQLException('SQL error'.($this->logQueries ? ': '.$e->getMessage() : ''));
}
/**
@ -351,42 +310,61 @@ class Database
public function cancelTransaction()
{
if ($this->getConnection()->inTransaction()) {
$this->getConnection()->rollback();
$this->getConnection()->rollBack();
}
}
/**
* Get a table instance
* Get a table object
*
* @access public
* @param string $table_name
* @param string $table
* @return Table
*/
public function table($table_name)
public function table($table)
{
return new Table($this, $table_name);
return new Table($this, $table);
}
/**
* Get a hashtable instance
* Get a hashtable object
*
* @access public
* @param string $table_name
* @param string $table
* @return Hashtable
*/
public function hashtable($table_name)
public function hashtable($table)
{
return new Hashtable($this, $table_name);
return new Hashtable($this, $table);
}
/**
* Get a schema instance
* Get a LOB object
*
* @access public
* @param string $table
* @return LargeObject
*/
public function largeObject($table)
{
return new LargeObject($this, $table);
}
/**
* Get a schema object
*
* @access public
* @param string $namespace
* @return Schema
*/
public function schema()
public function schema($namespace = null)
{
return new Schema($this);
$schema = new Schema($this);
if ($namespace !== null) {
$schema->setNamespace($namespace);
}
return $schema;
}
}
}

View File

@ -9,7 +9,8 @@ use PDOException;
/**
* Base Driver class
*
* @author Frederic Guillot
* @package PicoDb\Driver
* @author Frederic Guillot
*/
abstract class Base
{
@ -19,7 +20,7 @@ abstract class Base
* @access protected
* @var array
*/
protected $requiredAtttributes = array();
protected $requiredAttributes = array();
/**
* PDO connection
@ -119,7 +120,7 @@ abstract class Base
*/
public function __construct(array $settings)
{
foreach ($this->requiredAtttributes as $attribute) {
foreach ($this->requiredAttributes as $attribute) {
if (! isset($settings[$attribute])) {
throw new LogicException('This configuration parameter is missing: "'.$attribute.'"');
}
@ -185,8 +186,49 @@ abstract class Base
return true;
}
catch (PDOException $e) {
$this->pdo->rollback();
$this->pdo->rollBack();
return false;
}
}
/**
* Run EXPLAIN command
*
* @access public
* @param string $sql
* @param array $values
* @return array
*/
public function explain($sql, array $values)
{
return $this->getConnection()->query('EXPLAIN '.$this->getSqlFromPreparedStatement($sql, $values))->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Replace placeholder with values in prepared statement
*
* @access protected
* @param string $sql
* @param array $values
* @return string
*/
protected function getSqlFromPreparedStatement($sql, array $values)
{
foreach ($values as $value) {
$sql = substr_replace($sql, "'$value'", strpos($sql, '?'), 1);
}
return $sql;
}
/**
* Get database version
*
* @access public
* @return array
*/
public function getDatabaseVersion()
{
return $this->getConnection()->query('SELECT VERSION()')->fetchColumn();
}
}

View File

@ -0,0 +1,178 @@
<?php
namespace PicoDb\Driver;
use PDO;
/**
* Microsoft SQL Server Driver
*
* @package PicoDb\Driver
* @author Algy Taylor <thomas.taylor@cmft.nhs.uk>
*/
class Mssql extends Base
{
/**
* List of required settings options
*
* @access protected
* @var array
*/
protected $requiredAttributes = array(
'hostname',
'username',
'password',
'database',
);
/**
* Table to store the schema version
*
* @access private
* @var array
*/
private $schemaTable = 'schema_version';
/**
* Create a new PDO connection
*
* @access public
* @param array $settings
*/
public function createConnection(array $settings)
{
$dsn = 'sqlsrv:Server=' . $settings['hostname'] . ';Database=' . $settings['database'];
if (! empty($settings['port'])) {
$dsn .= ';port=' . $settings['port'];
}
$this->pdo = new PDO($dsn, $settings['username'], $settings['password']);
if (isset($settings['schema_table'])) {
$this->schemaTable = $settings['schema_table'];
}
}
/**
* Enable foreign keys
*
* @access public
*/
public function enableForeignKeys()
{
$this->pdo->exec('EXEC sp_MSforeachtable @command1="ALTER TABLE ? CHECK CONSTRAINT ALL"; GO;');
}
/**
* Disable foreign keys
*
* @access public
*/
public function disableForeignKeys()
{
$this->pdo->exec('EXEC sp_MSforeachtable @command1="ALTER TABLE ? NOCHECK CONSTRAINT ALL"; GO;');
}
/**
* Return true if the error code is a duplicate key
*
* @access public
* @param integer $code
* @return boolean
*/
public function isDuplicateKeyError($code)
{
return $code == 2601;
}
/**
* Escape identifier
*
* https://msdn.microsoft.com/en-us/library/ms175874.aspx
*
* @access public
* @param string $identifier
* @return string
*/
public function escape($identifier)
{
return '['.$identifier.']';
}
/**
* Get non standard operator
*
* @access public
* @param string $operator
* @return string
*/
public function getOperator($operator)
{
if ($operator === 'LIKE' || $operator === 'ILIKE') {
return 'LIKE';
}
return '';
}
/**
* Get last inserted id
*
* @access public
* @return integer
*/
public function getLastId()
{
return $this->pdo->lastInsertId();
}
/**
* Get current schema version
*
* @access public
* @return integer
*/
public function getSchemaVersion()
{
$this->pdo->exec("CREATE TABLE IF NOT EXISTS [".$this->schemaTable."] ([version] INT DEFAULT '0')");
$rq = $this->pdo->prepare('SELECT [version] FROM ['.$this->schemaTable.']');
$rq->execute();
$result = $rq->fetchColumn();
if ($result !== false) {
return (int) $result;
}
else {
$this->pdo->exec('INSERT INTO ['.$this->schemaTable.'] VALUES(0)');
}
return 0;
}
/**
* Set current schema version
*
* @access public
* @param integer $version
*/
public function setSchemaVersion($version)
{
$rq = $this->pdo->prepare('UPDATE ['.$this->schemaTable.'] SET [version]=?');
$rq->execute(array($version));
}
/**
* Run EXPLAIN command
*
* @param string $sql
* @param array $values
* @return array
*/
public function explain($sql, array $values)
{
$this->getConnection()->exec('SET SHOWPLAN_ALL ON');
return $this->getConnection()->query($this->getSqlFromPreparedStatement($sql, $values))->fetchAll(PDO::FETCH_ASSOC);
}
}

View File

@ -8,7 +8,8 @@ use PDOException;
/**
* Mysql Driver
*
* @author Frederic Guillot
* @package PicoDb\Driver
* @author Frederic Guillot
*/
class Mysql extends Base
{
@ -18,7 +19,7 @@ class Mysql extends Base
* @access protected
* @var array
*/
protected $requiredAtttributes = array(
protected $requiredAttributes = array(
'hostname',
'username',
'password',
@ -40,6 +41,27 @@ class Mysql extends Base
* @param array $settings
*/
public function createConnection(array $settings)
{
$this->pdo = new PDO(
$this->buildDsn($settings),
$settings['username'],
$settings['password'],
$this->buildOptions($settings)
);
if (isset($settings['schema_table'])) {
$this->schemaTable = $settings['schema_table'];
}
}
/**
* Build connection DSN
*
* @access protected
* @param array $settings
* @return string
*/
protected function buildDsn(array $settings)
{
$charset = empty($settings['charset']) ? 'utf8' : $settings['charset'];
$dsn = 'mysql:host='.$settings['hostname'].';dbname='.$settings['database'].';charset='.$charset;
@ -48,15 +70,35 @@ class Mysql extends Base
$dsn .= ';port='.$settings['port'];
}
return $dsn;
}
/**
* Build connection options
*
* @access protected
* @param array $settings
* @return array
*/
protected function buildOptions(array $settings)
{
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET sql_mode = STRICT_ALL_TABLES',
);
$this->pdo = new PDO($dsn, $settings['username'], $settings['password'], $options);
if (isset($settings['schema_table'])) {
$this->schemaTable = $settings['schema_table'];
if (! empty($settings['ssl_key'])) {
$options[PDO::MYSQL_ATTR_SSL_KEY] = $settings['ssl_key'];
}
if (! empty($settings['ssl_cert'])) {
$options[PDO::MYSQL_ATTR_SSL_CERT] = $settings['ssl_cert'];
}
if (! empty($settings['ssl_ca'])) {
$options[PDO::MYSQL_ATTR_SSL_CA] = $settings['ssl_ca'];
}
return $options;
}
/**
@ -141,7 +183,7 @@ class Mysql extends Base
*/
public function getSchemaVersion()
{
$this->pdo->exec("CREATE TABLE IF NOT EXISTS `".$this->schemaTable."` (`version` INT DEFAULT '0')");
$this->pdo->exec("CREATE TABLE IF NOT EXISTS `".$this->schemaTable."` (`version` INT DEFAULT '0') ENGINE=InnoDB CHARSET=utf8");
$rq = $this->pdo->prepare('SELECT `version` FROM `'.$this->schemaTable.'`');
$rq->execute();

View File

@ -8,7 +8,8 @@ use PDOException;
/**
* Postgres Driver
*
* @author Frederic Guillot
* @package PicoDb\Driver
* @author Frederic Guillot
*/
class Postgres extends Base
{
@ -18,7 +19,7 @@ class Postgres extends Base
* @access protected
* @var array
*/
protected $requiredAtttributes = array(
protected $requiredAttributes = array(
'hostname',
'username',
'password',
@ -169,4 +170,27 @@ class Postgres extends Base
$rq = $this->pdo->prepare('UPDATE '.$this->schemaTable.' SET version=?');
$rq->execute(array($version));
}
/**
* Run EXPLAIN command
*
* @param string $sql
* @param array $values
* @return array
*/
public function explain($sql, array $values)
{
return $this->getConnection()->query('EXPLAIN (FORMAT YAML) '.$this->getSqlFromPreparedStatement($sql, $values))->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Get database version
*
* @access public
* @return array
*/
public function getDatabaseVersion()
{
return $this->getConnection()->query('SHOW server_version')->fetchColumn();
}
}

View File

@ -8,7 +8,8 @@ use PDOException;
/**
* Sqlite Driver
*
* @author Frederic Guillot
* @package PicoDb\Driver
* @author Frederic Guillot
*/
class Sqlite extends Base
{
@ -18,7 +19,7 @@ class Sqlite extends Base
* @access protected
* @var array
*/
protected $requiredAtttributes = array('filename');
protected $requiredAttributes = array('filename');
/**
* Create a new PDO connection
@ -161,8 +162,32 @@ class Sqlite extends Base
return true;
}
catch (PDOException $e) {
$this->pdo->rollback();
$this->pdo->rollBack();
return false;
}
}
/**
* Run EXPLAIN command
*
* @access public
* @param string $sql
* @param array $values
* @return array
*/
public function explain($sql, array $values)
{
return $this->getConnection()->query('EXPLAIN QUERY PLAN '.$this->getSqlFromPreparedStatement($sql, $values))->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Get database version
*
* @access public
* @return array
*/
public function getDatabaseVersion()
{
return $this->getConnection()->query('SELECT sqlite_version()')->fetchColumn();
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace PicoDb;
use LogicException;
use PicoDb\Driver\Mssql;
use PicoDb\Driver\Mysql;
use PicoDb\Driver\Postgres;
use PicoDb\Driver\Sqlite;
/**
* Class DriverFactory
*
* @package PicoDb
* @author Frederic Guillot
*/
class DriverFactory
{
/**
* Get database driver from settings or environment URL
*
* @access public
* @param array $settings
* @return Mssql|Mysql|Postgres|Sqlite
*/
public static function getDriver(array $settings)
{
if (! isset($settings['driver'])) {
throw new LogicException('You must define a database driver');
}
switch ($settings['driver']) {
case 'sqlite':
return new Sqlite($settings);
case 'mssql':
return new Mssql($settings);
case 'mysql':
return new Mysql($settings);
case 'postgres':
return new Postgres($settings);
default:
throw new LogicException('This database driver is not supported');
}
}
}

View File

@ -5,10 +5,11 @@ namespace PicoDb;
use PDO;
/**
* Hashtable (key/value)
* HashTable (key/value)
*
* @author Frederic Guillot
* @author Mathias Kresin
* @package PicoDb
* @author Frederic Guillot
* @author Mathias Kresin
*/
class Hashtable extends Table
{
@ -33,7 +34,7 @@ class Hashtable extends Table
*
* @access public
* @param string $column
* @return Table
* @return $this
*/
public function columnKey($column)
{
@ -46,7 +47,7 @@ class Hashtable extends Table
*
* @access public
* @param string $column
* @return Table
* @return $this
*/
public function columnValue($column)
{
@ -84,7 +85,7 @@ class Hashtable extends Table
// setup to select columns in case that there are more than two
$this->columns($this->keyColumn, $this->valueColumn);
$rq = $this->db->execute($this->buildSelectQuery(), $this->condition->getValues());
$rq = $this->db->execute($this->buildSelectQuery(), $this->conditionBuilder->getValues());
$rows = $rq->fetchAll(PDO::FETCH_NUM);
foreach ($rows as $row) {

View File

@ -0,0 +1,167 @@
<?php
namespace PicoDb;
use PDO;
use PicoDb\Builder\InsertBuilder;
use PicoDb\Builder\UpdateBuilder;
/**
* Handle Large Objects (LOBs)
*
* @package PicoDb
* @author Frederic Guillot
*/
class LargeObject extends Table
{
/**
* Fetch large object as file descriptor
*
* This method is not compatible with Sqlite and Mysql (return a string instead of resource)
*
* @access public
* @param string $column
* @return resource
*/
public function findOneColumnAsStream($column)
{
$this->limit(1);
$this->columns($column);
$rq = $this->db->getStatementHandler()
->withSql($this->buildSelectQuery())
->withPositionalParams($this->conditionBuilder->getValues())
->execute();
$rq->bindColumn($column, $fd, PDO::PARAM_LOB);
$rq->fetch(PDO::FETCH_BOUND);
return $fd;
}
/**
* Fetch large object as string
*
* @access public
* @param string $column
* @return string
*/
public function findOneColumnAsString($column)
{
$fd = $this->findOneColumnAsStream($column);
if (is_string($fd)) {
return $fd;
}
return stream_get_contents($fd);
}
/**
* Insert large object from stream
*
* @access public
* @param string $blobColumn
* @param resource|string $blobDescriptor
* @param array $data
* @return bool
*/
public function insertFromStream($blobColumn, &$blobDescriptor, array $data = array())
{
$columns = array_merge(array($blobColumn), array_keys($data));
$this->db->startTransaction();
$result = $this->db->getStatementHandler()
->withSql(InsertBuilder::getInstance($this->db, $this->conditionBuilder)
->withTable($this->name)
->withColumns($columns)
->build()
)
->withNamedParams($data)
->withLobParam($blobColumn, $blobDescriptor)
->execute();
$this->db->closeTransaction();
return $result !== false;
}
/**
* Insert large object from file
*
* @access public
* @param string $blobColumn
* @param string $filename
* @param array $data
* @return bool
*/
public function insertFromFile($blobColumn, $filename, array $data = array())
{
$fp = fopen($filename, 'rb');
$result = $this->insertFromStream($blobColumn, $fp, $data);
fclose($fp);
return $result;
}
/**
* Insert large object from string
*
* @access public
* @param string $blobColumn
* @param string $blobData
* @param array $data
* @return bool
*/
public function insertFromString($blobColumn, &$blobData, array $data = array())
{
return $this->insertFromStream($blobColumn, $blobData, $data);
}
/**
* Update large object from stream
*
* @access public
* @param string $blobColumn
* @param resource $blobDescriptor
* @param array $data
* @return bool
*/
public function updateFromStream($blobColumn, &$blobDescriptor, array $data = array())
{
$values = array_merge(array_values($data), $this->conditionBuilder->getValues());
$columns = array_merge(array($blobColumn), array_keys($data));
$this->db->startTransaction();
$result = $this->db->getStatementHandler()
->withSql(UpdateBuilder::getInstance($this->db, $this->conditionBuilder)
->withTable($this->name)
->withColumns($columns)
->build()
)
->withPositionalParams($values)
->withLobParam($blobColumn, $blobDescriptor)
->execute();
$this->db->closeTransaction();
return $result !== false;
}
/**
* Update large object from file
*
* @access public
* @param string $blobColumn
* @param string $filename
* @param array $data
* @return bool
*/
public function updateFromFile($blobColumn, $filename, array $data = array())
{
$fp = fopen($filename, 'r');
$result = $this->updateFromStream($blobColumn, $fp, $data);
fclose($fp);
return $result;
}
}

View File

@ -7,7 +7,8 @@ use Exception;
/**
* SQLException
*
* @author Frederic Guillot
* @package PicoDb
* @author Frederic Guillot
*/
class SQLException extends Exception
{

View File

@ -7,7 +7,8 @@ use PDOException;
/**
* Schema migration class
*
* @author Frederic Guillot
* @package PicoDb
* @author Frederic Guillot
*/
class Schema
{
@ -19,6 +20,14 @@ class Schema
*/
protected $db = null;
/**
* Schema namespace
*
* @access protected
* @var string
*/
protected $namespace = '\Schema';
/**
* Constructor
*
@ -30,6 +39,30 @@ class Schema
$this->db = $db;
}
/**
* Set another namespace
*
* @access public
* @param string $namespace
* @return Schema
*/
public function setNamespace($namespace)
{
$this->namespace = $namespace;
return $this;
}
/**
* Get schema namespace
*
* @access public
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* Check the schema version and run the migrations
*
@ -59,25 +92,23 @@ class Schema
public function migrateTo($current_version, $next_version)
{
try {
$this->db->startTransaction();
$this->db->getDriver()->disableForeignKeys();
for ($i = $current_version + 1; $i <= $next_version; $i++) {
$this->db->startTransaction();
$this->db->getDriver()->disableForeignKeys();
$function_name = '\Schema\version_'.$i;
$function_name = $this->getNamespace().'\version_'.$i;
if (function_exists($function_name)) {
$this->db->setLogMessage('Running migration '.$function_name);
call_user_func($function_name, $this->db->getConnection());
}
}
$this->db->getDriver()->setSchemaVersion($i - 1);
$this->db->getDriver()->enableForeignKeys();
$this->db->closeTransaction();
}
catch (PDOException $e) {
$this->db->setLogMessage($function_name.' => '.$e->getMessage());
$this->db->getDriver()->setSchemaVersion($i);
$this->db->getDriver()->enableForeignKeys();
$this->db->closeTransaction();
}
} catch (PDOException $e) {
$this->db->setLogMessage($e->getMessage());
$this->db->cancelTransaction();
$this->db->getDriver()->enableForeignKeys();
return false;

View File

@ -0,0 +1,353 @@
<?php
namespace PicoDb;
use PDO;
use PDOException;
use PDOStatement;
/**
* Statement Handler
*
* @package PicoDb
* @author Frederic Guillot
*/
class StatementHandler
{
/**
* Database instance
*
* @access protected
* @var Database
*/
protected $db = null;
/**
* Flag to calculate query time
*
* @access protected
* @var boolean
*/
protected $stopwatch = false;
/**
* Start time
*
* @access protected
* @var float
*/
protected $startTime = 0;
/**
* Execution time of all queries
*
* @access protected
* @var float
*/
protected $executionTime = 0;
/**
* Flag to log generated SQL queries
*
* @access protected
* @var boolean
*/
protected $logQueries = false;
/**
* Run explain command on each query
*
* @access protected
* @var boolean
*/
protected $explain = false;
/**
* Number of SQL queries executed
*
* @access protected
* @var integer
*/
protected $nbQueries = 0;
/**
* SQL query
*
* @access protected
* @var string
*/
protected $sql = '';
/**
* Positional SQL parameters
*
* @access protected
* @var array
*/
protected $positionalParams = array();
/**
* Named SQL parameters
*
* @access protected
* @var array
*/
protected $namedParams = array();
/**
* Flag to use named params
*
* @access protected
* @var boolean
*/
protected $useNamedParams = false;
/**
* LOB params
*
* @access protected
* @var array
*/
protected $lobParams = array();
/**
* Constructor
*
* @access public
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* Enable query logging
*
* @access public
* @return $this
*/
public function withLogging()
{
$this->logQueries = true;
return $this;
}
/**
* Record query execution time
*
* @access public
* @return $this
*/
public function withStopWatch()
{
$this->stopwatch = true;
return $this;
}
/**
* Execute explain command on query
*
* @access public
* @return $this
*/
public function withExplain()
{
$this->explain = true;
return $this;
}
/**
* Set SQL query
*
* @access public
* @param string $sql
* @return $this
*/
public function withSql($sql)
{
$this->sql = $sql;
return $this;
}
/**
* Set positional parameters
*
* @access public
* @param array $params
* @return $this
*/
public function withPositionalParams(array $params)
{
$this->positionalParams = $params;
return $this;
}
/**
* Set named parameters
*
* @access public
* @param array $params
* @return $this
*/
public function withNamedParams(array $params)
{
$this->namedParams = $params;
$this->useNamedParams = true;
return $this;
}
/**
* Bind large object parameter
*
* @access public
* @param $name
* @param $fp
* @return $this
*/
public function withLobParam($name, &$fp)
{
$this->lobParams[$name] =& $fp;
return $this;
}
/**
* Get number of queries executed
*
* @access public
* @return int
*/
public function getNbQueries()
{
return $this->nbQueries;
}
/**
* Execute a prepared statement
*
* Note: returns false on duplicate keys instead of SQLException
*
* @access public
* @return PDOStatement|false
*/
public function execute()
{
try {
$this->beforeExecute();
$pdoStatement = $this->db->getConnection()->prepare($this->sql);
$this->bindParams($pdoStatement);
$pdoStatement->execute();
$this->afterExecute();
return $pdoStatement;
} catch (PDOException $e) {
return $this->handleSqlError($e);
}
}
/**
* Bind parameters to PDOStatement
*
* @access protected
* @param PDOStatement $pdoStatement
*/
protected function bindParams(PDOStatement $pdoStatement)
{
$i = 1;
foreach ($this->lobParams as $name => $variable) {
if (! $this->useNamedParams) {
$parameter = $i;
$i++;
} else {
$parameter = $name;
}
$pdoStatement->bindParam($parameter, $variable, PDO::PARAM_LOB);
}
foreach ($this->positionalParams as $value) {
$pdoStatement->bindValue($i, $value, PDO::PARAM_STR);
$i++;
}
foreach ($this->namedParams as $name => $value) {
$pdoStatement->bindValue($name, $value, PDO::PARAM_STR);
}
}
/**
* Method executed before query execution
*
* @access protected
*/
protected function beforeExecute()
{
if ($this->logQueries) {
$this->db->setLogMessage($this->sql);
}
if ($this->stopwatch) {
$this->startTime = microtime(true);
}
}
/**
* Method executed after query execution
*
* @access protected
*/
protected function afterExecute()
{
if ($this->stopwatch) {
$duration = microtime(true) - $this->startTime;
$this->executionTime += $duration;
$this->db->setLogMessage('query_duration='.$duration);
$this->db->setLogMessage('total_execution_time='.$this->executionTime);
}
if ($this->explain) {
$this->db->setLogMessages($this->db->getDriver()->explain($this->sql, $this->positionalParams));
}
$this->nbQueries++;
$this->cleanup();
}
/**
* Reset internal properties after execution
* The same object instance is used
*
* @access protected
*/
protected function cleanup()
{
$this->sql = '';
$this->useNamedParams = false;
$this->positionalParams = array();
$this->namedParams = array();
$this->lobParams = array();
}
/**
* Handle PDOException
*
* @access public
* @param PDOException $e
* @return bool
* @throws SQLException
*/
public function handleSqlError(PDOException $e)
{
$this->cleanup();
$this->db->cancelTransaction();
$this->db->setLogMessage($e->getMessage());
if ($this->db->getDriver()->isDuplicateKeyError($e->getCode())) {
return false;
}
throw new SQLException('SQL error'.($this->logQueries ? ': '.$e->getMessage() : ''));
}
}

View File

@ -4,27 +4,37 @@ namespace PicoDb;
use PDO;
use Closure;
use PicoDb\Builder\ConditionBuilder;
use PicoDb\Builder\InsertBuilder;
use PicoDb\Builder\UpdateBuilder;
/**
* Table
*
* @author Frederic Guillot
* @package PicoDb
* @author Frederic Guillot
*
* @method Table addCondition($sql)
* @method Table beginOr()
* @method Table closeOr()
* @method Table eq($column, $value)
* @method Table neq($column, $value)
* @method Table in($column, array $values)
* @method Table notin($column, array $values)
* @method Table like($column, $value)
* @method Table ilike($column, $value)
* @method Table gt($column, $value)
* @method Table lt($column, $value)
* @method Table gte($column, $value)
* @method Table lte($column, $value)
* @method Table isNull($column)
* @method Table notNull($column)
* @method $this addCondition($sql)
* @method $this beginOr()
* @method $this closeOr()
* @method $this eq($column, $value)
* @method $this neq($column, $value)
* @method $this in($column, array $values)
* @method $this inSubquery($column, Table $subquery)
* @method $this notIn($column, array $values)
* @method $this notInSubquery($column, Table $subquery)
* @method $this like($column, $value)
* @method $this ilike($column, $value)
* @method $this gt($column, $value)
* @method $this gtSubquery($column, Table $subquery)
* @method $this lt($column, $value)
* @method $this ltSubquery($column, Table $subquery)
* @method $this gte($column, $value)
* @method $this gteSubquery($column, Table $subquery)
* @method $this lte($column, $value)
* @method $this lteSubquery($column, Table $subquery)
* @method $this isNull($column)
* @method $this notNull($column)
*/
class Table
{
@ -40,10 +50,10 @@ class Table
/**
* Condition instance
*
* @access public
* @var Condition
* @access protected
* @var ConditionBuilder
*/
public $condition;
protected $conditionBuilder;
/**
* Database instance
@ -152,7 +162,7 @@ class Table
{
$this->db = $db;
$this->name = $name;
$this->condition = new Condition($db);
$this->conditionBuilder = new ConditionBuilder($db);
}
/**
@ -166,6 +176,17 @@ class Table
return $this->name;
}
/**
* Return ConditionBuilder object
*
* @access public
* @return ConditionBuilder
*/
public function getConditionBuilder()
{
return $this->conditionBuilder;
}
/**
* Insert or update
*
@ -175,47 +196,24 @@ class Table
*/
public function save(array $data)
{
return $this->condition->hasCondition() ? $this->update($data) : $this->insert($data);
return $this->conditionBuilder->hasCondition() ? $this->update($data) : $this->insert($data);
}
/**
* Update
*
* Note: Do not use `rowCount()` for update the behaviour is different across drivers
*
* @access public
* @param array $data
* @return boolean
*/
public function update(array $data = array())
{
$columns = array();
$values = array();
// Split columns and values
foreach ($data as $column => $value) {
$columns[] = $this->db->escapeIdentifier($column).'=?';
$values[] = $value;
}
// Sum columns
foreach ($this->sumColumns as $column => $value) {
$columns[] = $this->db->escapeIdentifier($column).'='.$this->db->escapeIdentifier($column).' + ?';
$values[] = $value;
}
// Append condition values
foreach ($this->condition->getValues() as $value) {
$values[] = $value;
}
// Build SQL query
$sql = sprintf(
'UPDATE %s SET %s %s',
$this->db->escapeIdentifier($this->name),
implode(', ', $columns),
$this->condition->build()
);
$values = array_merge(array_values($data), array_values($this->sumColumns), $this->conditionBuilder->getValues());
$sql = UpdateBuilder::getInstance($this->db, $this->conditionBuilder)
->withTable($this->name)
->withColumns(array_keys($data))
->withSumColumns(array_keys($this->sumColumns))
->build();
return $this->db->execute($sql, $values) !== false;
}
@ -229,20 +227,30 @@ class Table
*/
public function insert(array $data)
{
$columns = array();
return $this->db->getStatementHandler()
->withSql(InsertBuilder::getInstance($this->db, $this->conditionBuilder)
->withTable($this->name)
->withColumns(array_keys($data))
->build()
)
->withNamedParams($data)
->execute() !== false;
}
foreach ($data as $column => $value) {
$columns[] = $this->db->escapeIdentifier($column);
/**
* Insert a new row and return the ID of the primary key
*
* @access public
* @param array $data
* @return bool|int
*/
public function persist(array $data)
{
if ($this->insert($data)) {
return $this->db->getLastId();
}
$sql = sprintf(
'INSERT INTO %s (%s) VALUES (%s)',
$this->db->escapeIdentifier($this->name),
implode(', ', $columns),
implode(', ', array_fill(0, count($data), '?'))
);
return $this->db->execute($sql, array_values($data)) !== false;
return false;
}
/**
@ -256,10 +264,10 @@ class Table
$sql = sprintf(
'DELETE FROM %s %s',
$this->db->escapeIdentifier($this->name),
$this->condition->build()
$this->conditionBuilder->build()
);
$result = $this->db->execute($sql, $this->condition->getValues());
$result = $this->db->execute($sql, $this->conditionBuilder->getValues());
return $result->rowCount() > 0;
}
@ -271,7 +279,7 @@ class Table
*/
public function findAll()
{
$rq = $this->db->execute($this->buildSelectQuery(), $this->condition->getValues());
$rq = $this->db->execute($this->buildSelectQuery(), $this->conditionBuilder->getValues());
$results = $rq->fetchAll(PDO::FETCH_ASSOC);
if (is_callable($this->callback) && ! empty($results)) {
@ -291,7 +299,7 @@ class Table
public function findAllByColumn($column)
{
$this->columns = array($column);
$rq = $this->db->execute($this->buildSelectQuery(), $this->condition->getValues());
$rq = $this->db->execute($this->buildSelectQuery(), $this->conditionBuilder->getValues());
return $rq->fetchAll(PDO::FETCH_COLUMN, 0);
}
@ -322,7 +330,7 @@ class Table
$this->limit(1);
$this->columns = array($column);
return $this->db->execute($this->buildSelectQuery(), $this->condition->getValues())->fetchColumn();
return $this->db->execute($this->buildSelectQuery(), $this->conditionBuilder->getValues())->fetchColumn();
}
/**
@ -331,7 +339,7 @@ class Table
* @access public
* @param string $sql
* @param string $alias
* @return Table
* @return $this
*/
public function subquery($sql, $alias)
{
@ -348,11 +356,11 @@ class Table
public function exists()
{
$sql = sprintf(
'SELECT 1 FROM %s '.implode(' ', $this->joins).$this->condition->build(),
'SELECT 1 FROM %s '.implode(' ', $this->joins).$this->conditionBuilder->build(),
$this->db->escapeIdentifier($this->name)
);
$rq = $this->db->execute($sql, $this->condition->getValues());
$rq = $this->db->execute($sql, $this->conditionBuilder->getValues());
$result = $rq->fetchColumn();
return $result ? true : false;
@ -367,11 +375,11 @@ class Table
public function count()
{
$sql = sprintf(
'SELECT COUNT(*) FROM %s '.implode(' ', $this->joins).$this->condition->build().$this->sqlOrder.$this->sqlLimit.$this->sqlOffset,
'SELECT COUNT(*) FROM %s '.implode(' ', $this->joins).$this->conditionBuilder->build().$this->sqlOrder.$this->sqlLimit.$this->sqlOffset,
$this->db->escapeIdentifier($this->name)
);
$rq = $this->db->execute($sql, $this->condition->getValues());
$rq = $this->db->execute($sql, $this->conditionBuilder->getValues());
$result = $rq->fetchColumn();
return $result ? (int) $result : 0;
@ -387,17 +395,59 @@ class Table
public function sum($column)
{
$sql = sprintf(
'SELECT SUM(%s) FROM %s '.implode(' ', $this->joins).$this->condition->build().$this->sqlOrder.$this->sqlLimit.$this->sqlOffset,
'SELECT SUM(%s) FROM %s '.implode(' ', $this->joins).$this->conditionBuilder->build().$this->sqlOrder.$this->sqlLimit.$this->sqlOffset,
$this->db->escapeIdentifier($column),
$this->db->escapeIdentifier($this->name)
);
$rq = $this->db->execute($sql, $this->condition->getValues());
$rq = $this->db->execute($sql, $this->conditionBuilder->getValues());
$result = $rq->fetchColumn();
return $result ? (float) $result : 0;
}
/**
* Increment column value
*
* @access public
* @param string $column
* @param string $value
* @return boolean
*/
public function increment($column, $value)
{
$sql = sprintf(
'UPDATE %s SET %s=%s+%d '.$this->conditionBuilder->build(),
$this->db->escapeIdentifier($this->name),
$this->db->escapeIdentifier($column),
$this->db->escapeIdentifier($column),
$value
);
return $this->db->execute($sql, $this->conditionBuilder->getValues()) !== false;
}
/**
* Decrement column value
*
* @access public
* @param string $column
* @param string $value
* @return boolean
*/
public function decrement($column, $value)
{
$sql = sprintf(
'UPDATE %s SET %s=%s-%d '.$this->conditionBuilder->build(),
$this->db->escapeIdentifier($this->name),
$this->db->escapeIdentifier($column),
$this->db->escapeIdentifier($column),
$value
);
return $this->db->execute($sql, $this->conditionBuilder->getValues()) !== false;
}
/**
* Left join
*
@ -407,7 +457,7 @@ class Table
* @param string $local_column Local column
* @param string $local_table Local table
* @param string $alias Join table alias
* @return Table
* @return $this
*/
public function join($table, $foreign_column, $local_column, $local_table = '', $alias = '')
{
@ -430,7 +480,7 @@ class Table
* @param string $column1
* @param string $table2
* @param string $column2
* @return Table
* @return $this
*/
public function left($table1, $alias1, $column1, $table2, $column2)
{
@ -445,13 +495,37 @@ class Table
return $this;
}
/**
* Inner join
*
* @access public
* @param string $table1
* @param string $alias1
* @param string $column1
* @param string $table2
* @param string $column2
* @return $this
*/
public function inner($table1, $alias1, $column1, $table2, $column2)
{
$this->joins[] = sprintf(
'JOIN %s AS %s ON %s=%s',
$this->db->escapeIdentifier($table1),
$this->db->escapeIdentifier($alias1),
$this->db->escapeIdentifier($alias1).'.'.$this->db->escapeIdentifier($column1),
$this->db->escapeIdentifier($table2).'.'.$this->db->escapeIdentifier($column2)
);
return $this;
}
/**
* Order by
*
* @access public
* @param string $column Column name
* @param string $order Direction ASC or DESC
* @return Table
* @return $this
*/
public function orderBy($column, $order = self::SORT_ASC)
{
@ -473,7 +547,7 @@ class Table
*
* @access public
* @param string $column
* @return Table
* @return $this
*/
public function asc($column)
{
@ -486,7 +560,7 @@ class Table
*
* @access public
* @param string $column
* @return Table
* @return $this
*/
public function desc($column)
{
@ -499,7 +573,7 @@ class Table
*
* @access public
* @param integer $value
* @return Table
* @return $this
*/
public function limit($value)
{
@ -515,7 +589,7 @@ class Table
*
* @access public
* @param integer $value
* @return Table
* @return $this
*/
public function offset($value)
{
@ -530,7 +604,7 @@ class Table
* Group by
*
* @access public
* @return Table
* @return $this
*/
public function groupBy()
{
@ -543,7 +617,7 @@ class Table
*
* @access public
* @param string $select
* @return Table
* @return $this
*/
public function select($select)
{
@ -555,7 +629,7 @@ class Table
* Define the columns for the select
*
* @access public
* @return Table
* @return $this
*/
public function columns()
{
@ -569,7 +643,7 @@ class Table
* @access public
* @param string $column
* @param mixed $value
* @return Table
* @return $this
*/
public function sumColumn($column, $value)
{
@ -581,7 +655,7 @@ class Table
* Distinct
*
* @access public
* @return Table
* @return $this
*/
public function distinct()
{
@ -595,7 +669,7 @@ class Table
*
* @access public
* @param Closure|array $callback
* @return Table
* @return $this
*/
public function callback($callback)
{
@ -623,7 +697,7 @@ class Table
$this->sqlSelect,
$this->db->escapeIdentifier($this->name),
implode(' ', $this->joins),
$this->condition->build(),
$this->conditionBuilder->build(),
empty($this->groupBy) ? '' : 'GROUP BY '.implode(', ', $this->groupBy),
$this->sqlOrder,
$this->sqlLimit,
@ -637,11 +711,11 @@ class Table
* @access public
* @param string $name
* @param array $arguments
* @return Table
* @return $this
*/
public function __call($name, array $arguments)
{
call_user_func_array(array($this->condition, $name), $arguments);
call_user_func_array(array($this->conditionBuilder, $name), $arguments);
return $this;
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace PicoDb;
/**
* Class UrlParser
*
* @package PicoDb
* @author Frederic Guillot
*/
class UrlParser
{
/**
* URL
*
* @access private
* @var string
*/
private $url;
/**
* Constructor
*
* @access public
* @param string $environmentVariable
*/
public function __construct($environmentVariable = 'DATABASE_URL')
{
$this->url = getenv($environmentVariable);
}
/**
* Get object instance
*
* @access public
* @param string $environmentVariable
* @return static
*/
public static function getInstance($environmentVariable = 'DATABASE_URL')
{
return new static($environmentVariable);
}
/**
* Return true if the variable is defined
*
* @access public
* @return bool
*/
public function isEnvironmentVariableDefined()
{
return ! empty($this->url);
}
/**
* Get settings from URL
*
* @access public
* @param string $url
* @return array
*/
public function getSettings($url = '')
{
$url = $url ?: $this->url;
$components = parse_url($url);
if ($components === false) {
return array();
}
return array(
'driver' => $this->getUrlComponent($components, 'scheme'),
'username' => $this->getUrlComponent($components, 'user'),
'password' => $this->getUrlComponent($components, 'pass'),
'hostname' => $this->getUrlComponent($components, 'host'),
'port' => $this->getUrlComponent($components, 'port'),
'database' => ltrim($this->getUrlComponent($components, 'path'), '/'),
);
}
/**
* Get URL component
*
* @access private
* @param array $components
* @param string $component
* @return mixed|null
*/
private function getUrlComponent(array $components, $component)
{
return ! empty($components[$component]) ? $components[$component] : null;
}
}