*/
class Database
{
- private $connected = false;
+ protected $connected = false;
/**
* @var ConfigCache
*/
- private $configCache;
+ protected $configCache;
/**
* @var Profiler
*/
- private $profiler;
+ protected $profiler;
/**
* @var LoggerInterface
*/
- private $logger;
- private $server_info = '';
+ protected $logger;
+ protected $server_info = '';
/** @var PDO|mysqli */
- private $connection;
- private $driver;
+ protected $connection;
+ protected $driver;
private $error = false;
private $errorno = 0;
private $affected_rows = 0;
- private $in_transaction = false;
- private $in_retrial = false;
+ protected $in_transaction = false;
+ protected $in_retrial = false;
private $relation = [];
public function __construct(ConfigCache $configCache, Profiler $profiler, LoggerInterface $logger, array $server = [])
return true;
}
- private function performCommit()
+ protected function performCommit()
{
switch ($this->driver) {
case 'pdo':
namespace Friendica\Test;
-use PDO;
+use Friendica\Test\Util\Database\StaticDatabase;
use PHPUnit\DbUnit\DataSet\YamlDataSet;
use PHPUnit\DbUnit\TestCaseTrait;
use PHPUnit_Extensions_Database_DB_IDatabaseConnection;
{
use TestCaseTrait;
- // only instantiate pdo once for test clean-up/fixture load
- static private $pdo = null;
-
- // only instantiate PHPUnit_Extensions_Database_DB_IDatabaseConnection once per test
- private $conn = null;
-
/**
* Get database connection.
*
*/
protected function getConnection()
{
- $server = $_SERVER;
-
- if ($this->conn === null) {
- if (self::$pdo == null) {
-
- if (!empty($server['MYSQL_HOST'])
- && !empty($server['MYSQL_USERNAME'] || !empty($server['MYSQL_USER']))
- && $server['MYSQL_PASSWORD'] !== false
- && !empty($server['MYSQL_DATABASE'])) {
-
- $connect = "mysql:host=" . $server['MYSQL_HOST'] . ";dbname=" . $server['MYSQL_DATABASE'];
-
- if (!empty($server['MYSQL_PORT'])) {
- $connect .= ";port=" . $server['MYSQL_PORT'];
- }
-
- if (!empty($server['MYSQL_USERNAME'])) {
- $db_user = $server['MYSQL_USERNAME'];
- } else {
- $db_user = $server['MYSQL_USER'];
- }
-
- $db_pass = (string)$server['MYSQL_PASSWORD'];
-
- self::$pdo = @new PDO($connect, $db_user, $db_pass);
- self::$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
- }
- }
- $this->conn = $this->createDefaultDBConnection(self::$pdo, getenv('MYSQL_DATABASE'));
- }
-
- return $this->conn;
+ return $this->createDefaultDBConnection(StaticDatabase::getGlobConnection(), getenv('MYSQL_DATABASE'));
}
/**
--- /dev/null
+<?php
+
+namespace Friendica\Test\Util\Database;
+
+use PDO;
+use PDOException;
+
+/**
+ * This class extends native PDO one but allow nested transactions
+ * by using the SQL statements `SAVEPOINT', 'RELEASE SAVEPOINT' AND 'ROLLBACK SAVEPOINT'
+ */
+class ExtendedPDO extends PDO
+{
+ /**
+ * @var array Database drivers that support SAVEPOINT * statements.
+ */
+ protected static $_supportedDrivers = array("pgsql", "mysql");
+
+ /**
+ * @var int the current transaction depth
+ */
+ protected $_transactionDepth = 0;
+
+ /**
+ * @return int
+ */
+ public function getTransactionDepth()
+ {
+ return $this->_transactionDepth;
+ }
+
+ /**
+ * Test if database driver support savepoints
+ *
+ * @return bool
+ */
+ protected function hasSavepoint()
+ {
+ return in_array($this->getAttribute(PDO::ATTR_DRIVER_NAME),
+ self::$_supportedDrivers);
+ }
+
+
+ /**
+ * Start transaction
+ *
+ * @return bool|void
+ */
+ public function beginTransaction()
+ {
+ if($this->_transactionDepth == 0 || !$this->hasSavepoint()) {
+ parent::beginTransaction();
+ } else {
+ $this->exec("SAVEPOINT LEVEL{$this->_transactionDepth}");
+ }
+
+ $this->_transactionDepth++;
+ }
+
+ /**
+ * Commit current transaction
+ *
+ * @return bool|void
+ */
+ public function commit()
+ {
+ $this->_transactionDepth--;
+
+ if($this->_transactionDepth == 0 || !$this->hasSavepoint()) {
+ parent::commit();
+ } else {
+ $this->exec("RELEASE SAVEPOINT LEVEL{$this->_transactionDepth}");
+ }
+ }
+
+ /**
+ * Rollback current transaction,
+ *
+ * @throws PDOException if there is no transaction started
+ * @return bool|void
+ */
+ public function rollBack()
+ {
+
+ if ($this->_transactionDepth == 0) {
+ throw new PDOException('Rollback error : There is no transaction started');
+ }
+
+ $this->_transactionDepth--;
+
+ if($this->_transactionDepth == 0 || !$this->hasSavepoint()) {
+ parent::rollBack();
+ } else {
+ $this->exec("ROLLBACK TO SAVEPOINT LEVEL{$this->_transactionDepth}");
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace Friendica\Test\Util\Database;
+
+use Friendica\Database\Database;
+use PDO;
+use PDOException;
+
+class StaticDatabase extends Database
+{
+ /**
+ * @var ExtendedPDO
+ */
+ private static $staticConnection;
+
+ /**
+ * Override the behaviour of connect, due there is just one, static connection at all
+ *
+ * @return bool|void
+ */
+ public function connect()
+ {
+ if (!is_null($this->connection) && $this->connected()) {
+ return true;
+ }
+
+ if (!isset(self::$staticConnection)) {
+
+ $port = 0;
+ $serveraddr = trim($this->configCache->get('database', 'hostname'));
+ $serverdata = explode(':', $serveraddr);
+ $server = $serverdata[0];
+ if (count($serverdata) > 1) {
+ $port = trim($serverdata[1]);
+ }
+ $server = trim($server);
+ $user = trim($this->configCache->get('database', 'username'));
+ $pass = trim($this->configCache->get('database', 'password'));
+ $db = trim($this->configCache->get('database', 'database'));
+ $charset = trim($this->configCache->get('database', 'charset'));
+
+ if (!(strlen($server) && strlen($user))) {
+ return false;
+ }
+
+ $connect = "mysql:host=" . $server . ";dbname=" . $db;
+
+ if ($port > 0) {
+ $connect .= ";port=" . $port;
+ }
+
+ if ($charset) {
+ $connect .= ";charset=" . $charset;
+ }
+
+
+ try {
+ self::$staticConnection = @new ExtendedPDO($connect, $user, $pass);
+ self::$staticConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+ } catch (PDOException $e) {
+ /// @TODO At least log exception, don't ignore it!
+ }
+ }
+
+ $this->driver = 'pdo';
+ $this->connection = self::$staticConnection;
+ $this->connected = true;
+
+ return $this->connected;
+ }
+
+ /**
+ * Override the transaction since there are now hierachical transactions possible
+ *
+ * @return bool
+ */
+ public function transaction()
+ {
+ if (!$this->connection->inTransaction() && !$this->connection->beginTransaction()) {
+ return false;
+ }
+
+ $this->in_transaction = true;
+ return true;
+ }
+
+ /**
+ * @brief Does a commit
+ *
+ * @return boolean Was the command executed successfully?
+ */
+ public function commit()
+ {
+ if (!$this->performCommit()) {
+ return false;
+ }
+ $this->in_transaction = false;
+ return true;
+ }
+
+ /**
+ * @return ExtendedPDO
+ */
+ public static function getGlobConnection()
+ {
+ return self::$staticConnection;
+ }
+
+ public static function statCommit()
+ {
+ if (isset(self::$staticConnection)) {
+ while (self::$staticConnection->getTransactionDepth() > 0) {
+ self::$staticConnection->commit();
+ }
+ }
+ }
+
+ public static function statRollback()
+ {
+ if (isset(self::$staticConnection)) {
+ while (self::$staticConnection->getTransactionDepth() > 0) {
+ self::$staticConnection->rollBack();
+ }
+ }
+ }
+}
use Friendica\Core\PConfig;
use Friendica\Core\Protocol;
use Friendica\Core\System;
+use Friendica\Database\Database;
use Friendica\Network\HTTPException;
+use Friendica\Test\Util\Database\StaticDatabase;
use Monolog\Handler\TestHandler;
require_once __DIR__ . '/../../include/api.php';
*/
public function setUp()
{
- parent::setUp();
+ StaticDatabase::statRollback();
$dice = new Dice();
$dice = $dice->addRules(include __DIR__ . '/../../static/dependencies.config.php');
+ $dice = $dice->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true]);
BaseObject::setDependencyInjection($dice);
$this->app = BaseObject::getApp();
+ parent::setUp();
+
$this->app->argc = 1;
$this->app->argv = ['home'];
Config::set('system', 'theme', 'system_theme');
}
+ protected function tearDown()
+ {
+ StaticDatabase::statRollback();
+ }
+
/**
* Assert that an user array contains expected keys.
* @param array $user User array