diff --git a/common.php b/common.php index 13f5d26..2422645 100644 --- a/common.php +++ b/common.php @@ -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, please copy and paste this message and create a bug report:
'; $html .= '
';
         $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 .= '
'; diff --git a/composer.json b/composer.json index a8e8c98..2749692 100644 --- a/composer.json +++ b/composer.json @@ -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" }, diff --git a/models/item.php b/models/item.php index d07ae8e..ece9d6a 100644 --- a/models/item.php +++ b/models/item.php @@ -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') diff --git a/models/remember_me.php b/models/remember_me.php index 717e400..42ded92 100644 --- a/models/remember_me.php +++ b/models/remember_me.php @@ -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) diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 55ca5d8..98da40a 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -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', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index e928ae2..055e64b 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -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', diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index fb7b7c6..e4579fd 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -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" } ] diff --git a/vendor/fguillot/picodb/lib/PicoDb/Builder/BaseBuilder.php b/vendor/fguillot/picodb/lib/PicoDb/Builder/BaseBuilder.php new file mode 100644 index 0000000..e075ae3 --- /dev/null +++ b/vendor/fguillot/picodb/lib/PicoDb/Builder/BaseBuilder.php @@ -0,0 +1,86 @@ +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; + } +} diff --git a/vendor/fguillot/picodb/lib/PicoDb/Condition.php b/vendor/fguillot/picodb/lib/PicoDb/Builder/ConditionBuilder.php similarity index 60% rename from vendor/fguillot/picodb/lib/PicoDb/Condition.php rename to vendor/fguillot/picodb/lib/PicoDb/Builder/ConditionBuilder.php index b8c6de4..b0465b6 100644 --- a/vendor/fguillot/picodb/lib/PicoDb/Condition.php +++ b/vendor/fguillot/picodb/lib/PicoDb/Builder/ConditionBuilder.php @@ -1,13 +1,17 @@ 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 * diff --git a/vendor/fguillot/picodb/lib/PicoDb/Builder/InsertBuilder.php b/vendor/fguillot/picodb/lib/PicoDb/Builder/InsertBuilder.php new file mode 100644 index 0000000..9d06c40 --- /dev/null +++ b/vendor/fguillot/picodb/lib/PicoDb/Builder/InsertBuilder.php @@ -0,0 +1,36 @@ +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) + ); + } +} diff --git a/vendor/fguillot/picodb/lib/PicoDb/Builder/OrConditionBuilder.php b/vendor/fguillot/picodb/lib/PicoDb/Builder/OrConditionBuilder.php new file mode 100644 index 0000000..0defeaf --- /dev/null +++ b/vendor/fguillot/picodb/lib/PicoDb/Builder/OrConditionBuilder.php @@ -0,0 +1,43 @@ +conditions[] = $condition; + return $this; + } + + /** + * Build SQL + * + * @access public + * @return string + */ + public function build() + { + return '('.implode(' OR ', $this->conditions).')'; + } +} diff --git a/vendor/fguillot/picodb/lib/PicoDb/Builder/UpdateBuilder.php b/vendor/fguillot/picodb/lib/PicoDb/Builder/UpdateBuilder.php new file mode 100644 index 0000000..300ea9b --- /dev/null +++ b/vendor/fguillot/picodb/lib/PicoDb/Builder/UpdateBuilder.php @@ -0,0 +1,56 @@ +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() + ); + } +} diff --git a/vendor/fguillot/picodb/lib/PicoDb/Database.php b/vendor/fguillot/picodb/lib/PicoDb/Database.php index d3e4ab9..22c9d2f 100644 --- a/vendor/fguillot/picodb/lib/PicoDb/Database.php +++ b/vendor/fguillot/picodb/lib/PicoDb/Database.php @@ -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; } -} \ No newline at end of file +} diff --git a/vendor/fguillot/picodb/lib/PicoDb/Driver/Base.php b/vendor/fguillot/picodb/lib/PicoDb/Driver/Base.php index fd6aa40..790cd62 100644 --- a/vendor/fguillot/picodb/lib/PicoDb/Driver/Base.php +++ b/vendor/fguillot/picodb/lib/PicoDb/Driver/Base.php @@ -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(); + } } diff --git a/vendor/fguillot/picodb/lib/PicoDb/Driver/Mssql.php b/vendor/fguillot/picodb/lib/PicoDb/Driver/Mssql.php new file mode 100644 index 0000000..83e75af --- /dev/null +++ b/vendor/fguillot/picodb/lib/PicoDb/Driver/Mssql.php @@ -0,0 +1,178 @@ + + */ +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); + } +} diff --git a/vendor/fguillot/picodb/lib/PicoDb/Driver/Mysql.php b/vendor/fguillot/picodb/lib/PicoDb/Driver/Mysql.php index e1ae73b..4f3ca64 100644 --- a/vendor/fguillot/picodb/lib/PicoDb/Driver/Mysql.php +++ b/vendor/fguillot/picodb/lib/PicoDb/Driver/Mysql.php @@ -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(); diff --git a/vendor/fguillot/picodb/lib/PicoDb/Driver/Postgres.php b/vendor/fguillot/picodb/lib/PicoDb/Driver/Postgres.php index 4259101..a494cdc 100644 --- a/vendor/fguillot/picodb/lib/PicoDb/Driver/Postgres.php +++ b/vendor/fguillot/picodb/lib/PicoDb/Driver/Postgres.php @@ -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(); + } } diff --git a/vendor/fguillot/picodb/lib/PicoDb/Driver/Sqlite.php b/vendor/fguillot/picodb/lib/PicoDb/Driver/Sqlite.php index 67881ee..ea39f00 100644 --- a/vendor/fguillot/picodb/lib/PicoDb/Driver/Sqlite.php +++ b/vendor/fguillot/picodb/lib/PicoDb/Driver/Sqlite.php @@ -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(); + } } diff --git a/vendor/fguillot/picodb/lib/PicoDb/DriverFactory.php b/vendor/fguillot/picodb/lib/PicoDb/DriverFactory.php new file mode 100644 index 0000000..13151ba --- /dev/null +++ b/vendor/fguillot/picodb/lib/PicoDb/DriverFactory.php @@ -0,0 +1,45 @@ +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) { diff --git a/vendor/fguillot/picodb/lib/PicoDb/LargeObject.php b/vendor/fguillot/picodb/lib/PicoDb/LargeObject.php new file mode 100644 index 0000000..ba5e3b9 --- /dev/null +++ b/vendor/fguillot/picodb/lib/PicoDb/LargeObject.php @@ -0,0 +1,167 @@ +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; + } +} diff --git a/vendor/fguillot/picodb/lib/PicoDb/SQLException.php b/vendor/fguillot/picodb/lib/PicoDb/SQLException.php index 4d5cd68..7e57083 100644 --- a/vendor/fguillot/picodb/lib/PicoDb/SQLException.php +++ b/vendor/fguillot/picodb/lib/PicoDb/SQLException.php @@ -7,7 +7,8 @@ use Exception; /** * SQLException * - * @author Frederic Guillot + * @package PicoDb + * @author Frederic Guillot */ class SQLException extends Exception { diff --git a/vendor/fguillot/picodb/lib/PicoDb/Schema.php b/vendor/fguillot/picodb/lib/PicoDb/Schema.php index d0ba1c7..a028036 100644 --- a/vendor/fguillot/picodb/lib/PicoDb/Schema.php +++ b/vendor/fguillot/picodb/lib/PicoDb/Schema.php @@ -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; diff --git a/vendor/fguillot/picodb/lib/PicoDb/StatementHandler.php b/vendor/fguillot/picodb/lib/PicoDb/StatementHandler.php new file mode 100644 index 0000000..a7021b3 --- /dev/null +++ b/vendor/fguillot/picodb/lib/PicoDb/StatementHandler.php @@ -0,0 +1,353 @@ +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() : '')); + } +} diff --git a/vendor/fguillot/picodb/lib/PicoDb/Table.php b/vendor/fguillot/picodb/lib/PicoDb/Table.php index ca8dd69..09eb928 100644 --- a/vendor/fguillot/picodb/lib/PicoDb/Table.php +++ b/vendor/fguillot/picodb/lib/PicoDb/Table.php @@ -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; } } diff --git a/vendor/fguillot/picodb/lib/PicoDb/UrlParser.php b/vendor/fguillot/picodb/lib/PicoDb/UrlParser.php new file mode 100644 index 0000000..d8fcaf0 --- /dev/null +++ b/vendor/fguillot/picodb/lib/PicoDb/UrlParser.php @@ -0,0 +1,93 @@ +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; + } +}