getDriver()->disconnect(); } /** * Perform a reconnect of an existing database connection */ public static function reconnect() { return self::$db->getDriver()->reconnect(); } /** * Return the database object. * * @return IDriver */ public static function getConnection() { return self::$db->getDriver(); } /** * @brief Returns the MySQL server version string * * This function discriminate between the deprecated mysql API and the current * object-oriented mysqli API. Example of returned string: 5.5.46-0+deb8u1 * * @return string */ public static function serverInfo() { return self::$db->getDriver()->getServerInfo(); } /** * @brief Returns the selected database name * * @return string * @throws \Exception */ public static function databaseName() { return self::$db->getDatabaseName(); } /** * @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) { if (!self::$configCache->get('system', 'db_log_index')) { return; } // Don't explain an explain statement if (strtolower(substr($query, 0, 7)) == "explain") { return; } // Only do the explain on "select", "update" and "delete" if (!in_array(strtolower(substr($query, 0, 6)), ["select", "update", "delete"])) { return; } $r = self::p("EXPLAIN ".$query); if (!self::isResult($r)) { return; } $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(self::$configCache->get('system', 'db_loglimit_index')) > 0)) { $log = (in_array($row['key'], $watchlist) && ($row['rows'] >= intval(self::$configCache->get('system', 'db_loglimit_index')))); } else { $log = false; } 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 (in_array($row['key'], $blacklist) || ($row['key'] == "")) { $log = false; } if ($log) { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); @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". substr($query, 0, 2000)."\n", FILE_APPEND); } } } public static function escape($sql) { return self::$db->getDriver()->escape($sql); } public static function connected() { return self::$db->getDriver()->isConnected(true); } public static function p($sql) { $params = Utils::getParameters(func_get_args()); return self::$db->prepared($sql, $params); } public static function e($sql) { $params = Utils::getParameters(func_get_args()); return self::$db->execute($sql, $params); } /** * @brief Check if data exists * * @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)) { return false; } $fields = []; if (empty($condition)) { return DBStructure::existsTable($table); } reset($condition); $first_key = key($condition); if (!is_int($first_key)) { $fields = [$first_key]; } $stmt = self::select($table, $fields, $condition, ['limit' => 1]); if (is_bool($stmt)) { $retval = $stmt; } else { $retval = (self::numRows($stmt) > 0); } self::close($stmt); return $retval; } /** * Fetches the first row * * Please use DBA::selectFirst or DBA::exists whenever this is possible. * * @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()); $stmt = self::p($sql, $params); if (is_bool($stmt)) { $retval = $stmt; } else { $retval = self::fetch($stmt); } self::close($stmt); return $retval; } public static function affectedRows() { return self::$db->getAffectedRows(); } /** * @brief Returns the number of columns of a statement * * @param object Statement object * @return int Number of columns */ public static function columnCount($stmt) { if (!is_object($stmt)) { return 0; } switch (self::$driver) { case 'pdo': return $stmt->columnCount(); case 'mysqli': return $stmt->field_count; } return 0; } /** * @brief Returns the number of rows of a statement * * @param PDOStatement|mysqli_result|mysqli_stmt Statement object * @return int Number of rows */ public static function numRows($stmt) { if (!is_object($stmt)) { return 0; } switch (self::$driver) { case 'pdo': return $stmt->rowCount(); case 'mysqli': return $stmt->num_rows; } return 0; } public static function fetch($stmt) { return self::$db->fetch($stmt); } /** * @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 * * @return boolean was the insert successful? * @throws \Exception */ public static function insert($table, $param, $on_duplicate_update = false) { if (empty($table) || empty($param)) { Logger::log('Table and fields have to be set'); return false; } $sql = "INSERT INTO `".self::escape($table)."` (`".implode("`, `", array_keys($param))."`) VALUES (". substr(str_repeat("?, ", count($param)), 0, -2).")"; if ($on_duplicate_update) { $sql .= " ON DUPLICATE KEY UPDATE `".implode("` = ?, `", array_keys($param))."` = ?"; $values = array_values($param); $param = array_merge_recursive($values, $values); } return self::e($sql, $param); } /** * @brief Fetch the id of the last insert command * * @return integer Last inserted id */ public static function lastInsertId() { switch (self::$driver) { case 'pdo': $id = self::$connection->lastInsertId(); break; case 'mysqli': $id = self::$connection->insert_id; break; } return $id; } /** * @brief Locks a table for exclusive write access * * This function can be extended in the future to accept a table array as well. * * @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 if (self::$driver == 'pdo') { self::e("SET autocommit=0"); self::$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); } else { self::$connection->autocommit(false); } $success = self::e("LOCK TABLES `".self::escape($table)."` WRITE"); if (self::$driver == 'pdo') { self::$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); } if (!$success) { if (self::$driver == 'pdo') { self::e("SET autocommit=1"); } else { self::$connection->autocommit(true); } } else { self::$in_transaction = true; } return $success; } /** * @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 self::performCommit(); if (self::$driver == 'pdo') { self::$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); } $success = self::e("UNLOCK TABLES"); if (self::$driver == 'pdo') { self::$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); self::e("SET autocommit=1"); } else { self::$connection->autocommit(true); } self::$in_transaction = false; return $success; } public static function transaction() { self::$db->transaction(); } public static function commit() { return self::$db->commit(); } public static function rollback() { return self::$db->rollback(); } public static function delete($table, array $conditions, array $options = []) { return self::$db->delete($table, $conditions, (isset($options['cascade']) ? $options['cascade'] : true)); } public static function update($table, $fields, $condition, $old_fields = []) { return self::$db->select($table, $fields, $condition, $old_fields); } public static function selectFirst($table, array $fields = [], array $condition = [], $params = []) { return self::$db->selectFirst($table, $fields, $condition, $params); } public static function select($table, array $fields = [], array $condition = [], array $params = []) { return self::$db->select($table, $fields, $condition, $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 * * @return int * * Example: * $table = "item"; * * $condition = ["uid" => 1, "network" => 'dspr']; * or: * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr']; * * $count = DBA::count($table, $condition); * @throws \Exception */ public static function count($table, array $condition = []) { if ($table == '') { return false; } $condition_string = self::buildCondition($condition); $sql = "SELECT COUNT(*) AS `count` FROM `".$table."`".$condition_string; $row = self::fetchFirst($sql, $condition); return $row['count']; } /** * @brief Returns the SQL parameter string built from the provided parameter array * * @param array $params * @return string */ public static function buildParameter(array $params = []) { $order_string = ''; if (isset($params['order'])) { $order_string = " ORDER BY "; foreach ($params['order'] AS $fields => $order) { if (!is_int($fields)) { $order_string .= "`" . $fields . "` " . ($order ? "DESC" : "ASC") . ", "; } else { $order_string .= "`" . $order . "`, "; } } $order_string = substr($order_string, 0, -2); } $limit_string = ''; if (isset($params['limit']) && is_numeric($params['limit'])) { $limit_string = " LIMIT " . intval($params['limit']); } if (isset($params['limit']) && is_array($params['limit'])) { $limit_string = " LIMIT " . intval($params['limit'][0]) . ", " . intval($params['limit'][1]); } return $order_string.$limit_string; } /** * @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) { if (is_bool($stmt)) { return $stmt; } $data = []; while ($row = self::fetch($stmt)) { $data[] = $row; } if ($do_close) { self::close($stmt); } return $data; } /** * @brief Returns the error number of the last query * * @return string Error number (0 if no error) */ public static function errorNo() { return self::$errorno; } /** * @brief Returns the error message of the last query * * @return string Error message ('' if no error) */ public static function errorMessage() { return self::$error; } public static function close($stmt) { self::$db->close($stmt); } /** * @brief Return a list of database processes * * @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); $processes = 0; $states = []; foreach ($data as $process) { $state = trim($process["State"]); // Filter out all non blocking processes if (!in_array($state, ["", "init", "statistics", "updating"])) { ++$states[$state]; ++$processes; } } $statelist = ""; foreach ($states as $state => $usage) { if ($statelist != "") { $statelist .= ", "; } $statelist .= $state.": ".$usage; } return(["list" => $statelist, "amount" => $processes]); } /** * Checks if $array is a filled array with at least one entry. * * @param mixed $array A filled array with at least one entry * * @return boolean Whether $array is a filled array or an object with rows */ public static function isResult($array) { // It could be a return value from an update statement if (is_bool($array)) { return $array; } if (is_object($array)) { return self::numRows($array) > 0; } return (is_array($array) && (count($array) > 0)); } /** * @brief Escapes a whole array * * @param mixed $arr Array with values to be escaped * @param boolean $add_quotation add quotation marks for string values * @return void */ public static function escapeArray(&$arr, $add_quotation = false) { array_walk($arr, 'self::escapeArrayCallback', $add_quotation); } }