namespace Friendica\Database;
-// Do not use Core\Config in this class at risk of infinite loop.
-// Please use App->getConfigVariable() instead.
-//use Friendica\Core\Config;
-
+use Friendica\Core\Config\Cache\IConfigCache;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Util\DateTimeFormat;
+use Friendica\Util\Profiler;
use mysqli;
use mysqli_result;
use mysqli_stmt;
use PDOException;
use PDOStatement;
-require_once 'include/dba.php';
-
/**
* @class MySQL database class
*
public static $connected = false;
+ /**
+ * @var IConfigCache
+ */
+ private static $configCache;
+ /**
+ * @var Profiler
+ */
+ private static $profiler;
+ /**
+ * @var string
+ */
+ private static $basePath;
private static $server_info = '';
private static $connection;
private static $driver;
private static $db_name = '';
private static $db_charset = '';
- public static function connect($serveraddr, $user, $pass, $db, $charset = null)
+ public static function connect($basePath, IConfigCache $configCache, Profiler $profiler, $serveraddr, $user, $pass, $db, $charset = null)
{
if (!is_null(self::$connection) && self::connected()) {
return true;
}
// We are storing these values for being able to perform a reconnect
+ self::$basePath = $basePath;
+ self::$configCache = $configCache;
+ self::$profiler = $profiler;
self::$db_serveraddr = $serveraddr;
self::$db_user = $user;
self::$db_pass = $pass;
public static function reconnect() {
self::disconnect();
- $ret = self::connect(self::$db_serveraddr, self::$db_user, self::$db_pass, self::$db_name, self::$db_charset);
+ $ret = self::connect(self::$basePath, self::$configCache, self::$profiler, self::$db_serveraddr, self::$db_user, self::$db_pass, self::$db_name, self::$db_charset);
return $ret;
}
* @brief Returns the selected database name
*
* @return string
+ * @throws \Exception
*/
public static function databaseName() {
$ret = self::p("SELECT DATABASE() AS `db`");
* @brief Analyze a database query and log this if some conditions are met.
*
* @param string $query The database query that will be analyzed
+ * @throws \Exception
*/
private static function logIndex($query) {
- $a = get_app();
- if (!$a->getConfigVariable('system', 'db_log_index')) {
+ if (!self::$configCache->get('system', 'db_log_index')) {
return;
}
return;
}
- $watchlist = explode(',', $a->getConfigVariable('system', 'db_log_index_watch'));
- $blacklist = explode(',', $a->getConfigVariable('system', 'db_log_index_blacklist'));
+ $watchlist = explode(',', self::$configCache->get('system', 'db_log_index_watch'));
+ $blacklist = explode(',', self::$configCache->get('system', 'db_log_index_blacklist'));
while ($row = self::fetch($r)) {
- if ((intval($a->getConfigVariable('system', 'db_loglimit_index')) > 0)) {
+ if ((intval(self::$configCache->get('system', 'db_loglimit_index')) > 0)) {
$log = (in_array($row['key'], $watchlist) &&
- ($row['rows'] >= intval($a->getConfigVariable('system', 'db_loglimit_index'))));
+ ($row['rows'] >= intval(self::$configCache->get('system', 'db_loglimit_index'))));
} else {
$log = false;
}
- if ((intval($a->getConfigVariable('system', 'db_loglimit_index_high')) > 0) && ($row['rows'] >= intval($a->getConfigVariable('system', 'db_loglimit_index_high')))) {
+ if ((intval(self::$configCache->get('system', 'db_loglimit_index_high')) > 0) && ($row['rows'] >= intval(self::$configCache->get('system', 'db_loglimit_index_high')))) {
$log = true;
}
if ($log) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
- @file_put_contents($a->getConfigVariable('system', 'db_log_index'), DateTimeFormat::utcNow()."\t".
+ @file_put_contents(self::$configCache->get('system', 'db_log_index'), DateTimeFormat::utcNow()."\t".
$row['key']."\t".$row['rows']."\t".$row['Extra']."\t".
basename($backtrace[1]["file"])."\t".
$backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
*
* @param string $sql SQL statement
* @return bool|object statement object or result object
+ * @throws \Exception
*/
public static function p($sql) {
- $a = get_app();
$stamp1 = microtime(true);
if ((substr_count($sql, '?') != count($args)) && (count($args) > 0)) {
// Question: Should we continue or stop the query here?
- Logger::log('Parameter mismatch. Query "'.$sql.'" - Parameters '.print_r($args, true), Logger::DEBUG);
+ Logger::warning('Query parameters mismatch.', ['query' => $sql, 'args' => $args, 'callstack' => System::callstack()]);
}
$sql = self::cleanQuery($sql);
$orig_sql = $sql;
- if ($a->getConfigValue('system', 'db_callstack')) {
+ if (self::$configCache->get('system', 'db_callstack') !== null) {
$sql = "/*".System::callstack()." */ ".$sql;
}
self::$errorno = $errorno;
}
- $a->saveTimestamp($stamp1, 'database');
+ self::$profiler->saveTimestamp($stamp1, 'database', System::callstack());
- if ($a->getConfigValue('system', 'db_log')) {
+ if (self::$configCache->get('system', 'db_log')) {
$stamp2 = microtime(true);
$duration = (float)($stamp2 - $stamp1);
- if (($duration > $a->getConfigValue('system', 'db_loglimit'))) {
+ if (($duration > self::$configCache->get('system', 'db_loglimit'))) {
$duration = round($duration, 3);
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
- @file_put_contents($a->getConfigValue('system', 'db_log'), DateTimeFormat::utcNow()."\t".$duration."\t".
+ @file_put_contents(self::$configCache->get('system', 'db_log'), DateTimeFormat::utcNow()."\t".$duration."\t".
basename($backtrace[1]["file"])."\t".
$backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
substr(self::replaceParameters($sql, $args), 0, 2000)."\n", FILE_APPEND);
*
* @param string $sql SQL statement
* @return boolean Was the query successfull? False is returned only if an error occurred
+ * @throws \Exception
*/
public static function e($sql) {
- $a = get_app();
$stamp = microtime(true);
self::$errorno = $errorno;
}
- $a->saveTimestamp($stamp, "database_write");
+ self::$profiler->saveTimestamp($stamp, "database_write", System::callstack());
return $retval;
}
/**
* @brief Check if data exists
*
- * @param string $table Table name
- * @param array $condition array of fields for condition
+ * @param string $table Table name
+ * @param array $condition array of fields for condition
*
* @return boolean Are there rows for that condition?
+ * @throws \Exception
*/
public static function exists($table, $condition) {
if (empty($table)) {
* @brief Fetches the first row
* @param string $sql SQL statement
* @return array first row of query
+ * @throws \Exception
*/
public static function fetchFirst($sql) {
$params = self::getParam(func_get_args());
* @return array current row
*/
public static function fetch($stmt) {
- $a = get_app();
$stamp1 = microtime(true);
}
}
- $a->saveTimestamp($stamp1, 'database');
+ self::$profiler->saveTimestamp($stamp1, 'database', System::callstack());
return $columns;
}
/**
* @brief Insert a row into a table
*
- * @param string $table Table name
- * @param array $param parameter array
- * @param bool $on_duplicate_update Do an update on a duplicate entry
+ * @param string $table Table name
+ * @param array $param parameter array
+ * @param bool $on_duplicate_update Do an update on a duplicate entry
*
* @return boolean was the insert successful?
+ * @throws \Exception
*/
public static function insert($table, $param, $on_duplicate_update = false) {
* @param string $table Table name
*
* @return boolean was the lock successful?
+ * @throws \Exception
*/
public static function lock($table) {
// See here: https://dev.mysql.com/doc/refman/5.7/en/lock-tables-and-transactions.html
* @brief Unlocks all locked tables
*
* @return boolean was the unlock successful?
+ * @throws \Exception
*/
public static function unlock() {
// See here: https://dev.mysql.com/doc/refman/5.7/en/lock-tables-and-transactions.html
* This process must only be started once, since the value is cached.
*/
private static function buildRelationData() {
- $definition = DBStructure::definition();
+ $definition = DBStructure::definition(self::$basePath);
foreach ($definition AS $table => $structure) {
foreach ($structure['fields'] AS $field => $field_struct) {
/**
* @brief Delete a row from a table
*
- * @param string $table Table name
- * @param array $conditions Field condition(s)
- * @param array $options
- * - cascade: If true we delete records in other tables that depend on the one we're deleting through
+ * @param string $table Table name
+ * @param array $conditions Field condition(s)
+ * @param array $options
+ * - cascade: If true we delete records in other tables that depend on the one we're deleting through
* relations (default: true)
- * @param boolean $in_process Internal use: Only do a commit after the last delete
- * @param array $callstack Internal use: prevent endless loops
+ * @param array $callstack Internal use: prevent endless loops
*
- * @return boolean|array was the delete successful? When $in_process is set: deletion data
+ * @return boolean was the delete successful?
+ * @throws \Exception
*/
- public static function delete($table, array $conditions, array $options = [], $in_process = false, array &$callstack = [])
+ public static function delete($table, array $conditions, array $options = [], array &$callstack = [])
{
if (empty($table) || empty($conditions)) {
Logger::log('Table and conditions have to be set');
if ((count($conditions) == 1) && ($field == array_keys($conditions)[0])) {
foreach ($rel_def AS $rel_table => $rel_fields) {
foreach ($rel_fields AS $rel_field) {
- $retval = self::delete($rel_table, [$rel_field => array_values($conditions)[0]], $options, true, $callstack);
- $commands = array_merge($commands, $retval);
+ self::delete($rel_table, [$rel_field => array_values($conditions)[0]], $options, $callstack);
}
}
// We quit when this key already exists in the callstack.
} elseif (!isset($callstack[$qkey])) {
-
$callstack[$qkey] = true;
// Fetch all rows that are to be deleted
$data = self::select($table, [$field], $conditions);
while ($row = self::fetch($data)) {
- // Now we accumulate the delete commands
- $retval = self::delete($table, [$field => $row[$field]], $options, true, $callstack);
- $commands = array_merge($commands, $retval);
+ self::delete($table, [$field => $row[$field]], $options, $callstack);
}
self::close($data);
}
}
- if (!$in_process) {
- // Now we finalize the process
- $do_transaction = !self::$in_transaction;
+ // Now we finalize the process
+ $do_transaction = !self::$in_transaction;
- if ($do_transaction) {
- self::transaction();
- }
+ if ($do_transaction) {
+ self::transaction();
+ }
- $compacted = [];
- $counter = [];
+ $compacted = [];
+ $counter = [];
- foreach ($commands AS $command) {
- $conditions = $command['conditions'];
- reset($conditions);
- $first_key = key($conditions);
+ foreach ($commands AS $command) {
+ $conditions = $command['conditions'];
+ reset($conditions);
+ $first_key = key($conditions);
- $condition_string = self::buildCondition($conditions);
+ $condition_string = self::buildCondition($conditions);
- if ((count($command['conditions']) > 1) || is_int($first_key)) {
- $sql = "DELETE FROM `" . $command['table'] . "`" . $condition_string;
- Logger::log(self::replaceParameters($sql, $conditions), Logger::DATA);
+ if ((count($command['conditions']) > 1) || is_int($first_key)) {
+ $sql = "DELETE FROM `" . $command['table'] . "`" . $condition_string;
+ Logger::log(self::replaceParameters($sql, $conditions), Logger::DATA);
- if (!self::e($sql, $conditions)) {
- if ($do_transaction) {
- self::rollback();
- }
- return false;
- }
- } else {
- $key_table = $command['table'];
- $key_condition = array_keys($command['conditions'])[0];
- $value = array_values($command['conditions'])[0];
-
- // Split the SQL queries in chunks of 100 values
- // We do the $i stuff here to make the code better readable
- $i = isset($counter[$key_table][$key_condition]) ? $counter[$key_table][$key_condition] : 0;
- if (isset($compacted[$key_table][$key_condition][$i]) && count($compacted[$key_table][$key_condition][$i]) > 100) {
- ++$i;
+ if (!self::e($sql, $conditions)) {
+ if ($do_transaction) {
+ self::rollback();
}
-
- $compacted[$key_table][$key_condition][$i][$value] = $value;
- $counter[$key_table][$key_condition] = $i;
+ return false;
}
+ } else {
+ $key_table = $command['table'];
+ $key_condition = array_keys($command['conditions'])[0];
+ $value = array_values($command['conditions'])[0];
+
+ // Split the SQL queries in chunks of 100 values
+ // We do the $i stuff here to make the code better readable
+ $i = isset($counter[$key_table][$key_condition]) ? $counter[$key_table][$key_condition] : 0;
+ if (isset($compacted[$key_table][$key_condition][$i]) && count($compacted[$key_table][$key_condition][$i]) > 100) {
+ ++$i;
+ }
+
+ $compacted[$key_table][$key_condition][$i][$value] = $value;
+ $counter[$key_table][$key_condition] = $i;
}
- foreach ($compacted AS $table => $values) {
- foreach ($values AS $field => $field_value_list) {
- foreach ($field_value_list AS $field_values) {
- $sql = "DELETE FROM `" . $table . "` WHERE `" . $field . "` IN (" .
- substr(str_repeat("?, ", count($field_values)), 0, -2) . ");";
+ }
+ foreach ($compacted AS $table => $values) {
+ foreach ($values AS $field => $field_value_list) {
+ foreach ($field_value_list AS $field_values) {
+ $sql = "DELETE FROM `" . $table . "` WHERE `" . $field . "` IN (" .
+ substr(str_repeat("?, ", count($field_values)), 0, -2) . ");";
- Logger::log(self::replaceParameters($sql, $field_values), Logger::DATA);
+ Logger::log(self::replaceParameters($sql, $field_values), Logger::DATA);
- if (!self::e($sql, $field_values)) {
- if ($do_transaction) {
- self::rollback();
- }
- return false;
+ if (!self::e($sql, $field_values)) {
+ if ($do_transaction) {
+ self::rollback();
}
+ return false;
}
}
}
- if ($do_transaction) {
- self::commit();
- }
- return true;
}
-
- return $commands;
+ if ($do_transaction) {
+ self::commit();
+ }
+ return true;
}
/**
* Only set $old_fields to a boolean value when you are sure that you will update a single row.
* When you set $old_fields to "true" then $fields must contain all relevant fields!
*
- * @param string $table Table name
- * @param array $fields contains the fields that are updated
- * @param array $condition condition array with the key values
+ * @param string $table Table name
+ * @param array $fields contains the fields that are updated
+ * @param array $condition condition array with the key values
* @param array|boolean $old_fields array with the old field values that are about to be replaced (true = update on duplicate)
*
* @return boolean was the update successfull?
+ * @throws \Exception
*/
public static function update($table, $fields, $condition, $old_fields = []) {
foreach ($old_fields AS $fieldname => $content) {
if (isset($fields[$fieldname])) {
- if ($fields[$fieldname] == $content) {
+ if (($fields[$fieldname] == $content) && !is_null($content)) {
unset($fields[$fieldname]);
} else {
$do_update = true;
* @param array $condition
* @param array $params
* @return bool|array
- * @see self::select
+ * @throws \Exception
+ * @see self::select
*/
public static function selectFirst($table, array $fields = [], array $condition = [], $params = [])
{
* $params = array("order" => array("id", "received" => true), "limit" => 10);
*
* $data = DBA::select($table, $fields, $condition, $params);
+ * @throws \Exception
*/
public static function select($table, array $fields = [], array $condition = [], array $params = [])
{
/**
* @brief Counts the rows from a table satisfying the provided condition
*
- * @param string $table Table name
- * @param array $condition array of fields for condition
+ * @param string $table Table name
+ * @param array $condition array of fields for condition
*
* @return int
*
* $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
*
* $count = DBA::count($table, $condition);
+ * @throws \Exception
*/
public static function count($table, array $condition = [])
{
}
$limit_string = '';
- if (isset($params['limit']) && is_int($params['limit'])) {
+ if (isset($params['limit']) && is_numeric($params['limit'])) {
$limit_string = " LIMIT " . intval($params['limit']);
}
* @brief Fills an array with data from a query
*
* @param object $stmt statement object
+ * @param bool $do_close
* @return array Data array
*/
public static function toArray($stmt, $do_close = true) {
* @return boolean was the close successful?
*/
public static function close($stmt) {
- $a = get_app();
$stamp1 = microtime(true);
break;
}
- $a->saveTimestamp($stamp1, 'database');
+ self::$profiler->saveTimestamp($stamp1, 'database', System::callstack());
return $ret;
}
* @return array
* 'list' => List of processes, separated in their different states
* 'amount' => Number of concurrent database processes
+ * @throws \Exception
*/
public static function processlist()
{
$ret = self::p("SHOW PROCESSLIST");
$data = self::toArray($ret);
- $s = [];
-
$processes = 0;
$states = [];
foreach ($data as $process) {