diff --git a/include/dba.php b/include/dba.php index 5066dcd56d..76791911a3 100644 --- a/include/dba.php +++ b/include/dba.php @@ -10,6 +10,7 @@ require_once('include/datetime.php'); * When logging, all binary info is converted to text and html entities are escaped so that * the debugging stream is safe to view within both terminals and web pages. * + * This class is for the low level database stuff that does driver specific things. */ class dba { @@ -21,6 +22,7 @@ class dba { public $connected = false; public $error = false; private $_server_info = ''; + private static $dbo; function __construct($server, $user, $pass, $db, $install = false) { $a = get_app(); @@ -93,6 +95,8 @@ class dba { } } $a->save_timestamp($stamp1, "network"); + + self::$dbo = $this; } /** @@ -131,30 +135,6 @@ class dba { return $r[0]['db']; } - /** - * @brief Returns the number of rows - * - * @return integer - */ - public function num_rows() { - if (!$this->result) { - return 0; - } - - switch ($this->driver) { - case 'pdo': - $rows = $this->result->rowCount(); - break; - case 'mysqli': - $rows = $this->result->num_rows; - break; - case 'mysql': - $rows = mysql_num_rows($this->result); - break; - } - return $rows; - } - /** * @brief Analyze a database query and log this if some conditions are met. * @@ -249,7 +229,7 @@ class dba { break; } $stamp2 = microtime(true); - $duration = (float)($stamp2-$stamp1); + $duration = (float)($stamp2 - $stamp1); $a->save_timestamp($stamp1, "database"); @@ -379,41 +359,6 @@ class dba { return($r); } - public function qfetch() { - $x = false; - - if ($this->result) { - switch ($this->driver) { - case 'pdo': - $x = $this->result->fetch(PDO::FETCH_ASSOC); - break; - case 'mysqli': - $x = $this->result->fetch_array(MYSQLI_ASSOC); - break; - case 'mysql': - $x = mysql_fetch_array($this->result, MYSQL_ASSOC); - break; - } - } - return($x); - } - - public function qclose() { - if ($this->result) { - switch ($this->driver) { - case 'pdo': - $this->result->closeCursor(); - break; - case 'mysqli': - $this->result->free_result(); - break; - case 'mysql': - mysql_free_result($this->result); - break; - } - } - } - public function dbg($dbg) { $this->debug = $dbg; } @@ -497,6 +442,285 @@ class dba { } return $sql; } + + /** + * @brief Replaces the ? placeholders with the parameters in the $args array + * + * @param string $sql SQL query + * @param array $args The parameters that are to replace the ? placeholders + * @return string The replaced SQL query + */ + static private function replace_parameters($sql, $args) { + $offset = 0; + foreach ($args AS $param => $value) { + if (is_int($args[$param]) OR is_float($args[$param])) { + $replace = intval($args[$param]); + } else { + $replace = "'".dbesc($args[$param])."'"; + } + + $pos = strpos($sql, '?', $offset); + if ($pos !== false) { + $sql = substr_replace($sql, $replace, $pos, 1); + } + $offset = $pos + strlen($replace); + } + return $sql; + } + + /** + * @brief Executes a prepared statement that returns data + * @usage Example: $r = p("SELECT * FROM `item` WHERE `guid` = ?", $guid); + * @param string $sql SQL statement + * @return object statement object + */ + static public function p($sql) { + $a = get_app(); + + $stamp1 = microtime(true); + + $args = func_get_args(); + unset($args[0]); + + if (!self::$dbo OR !self::$dbo->connected) { + return false; + } + + $sql = self::$dbo->any_value_fallback($sql); + + if (x($a->config,'system') && x($a->config['system'], 'db_callstack')) { + $sql = "/*".$a->callstack()." */ ".$sql; + } + + switch (self::$dbo->driver) { + case 'pdo': + if (!$stmt = self::$dbo->db->prepare($sql)) { + $errorInfo = self::$dbo->db->errorInfo(); + self::$dbo->error = $errorInfo[2]; + self::$dbo->errorno = $errorInfo[1]; + $retval = false; + break; + } + + foreach ($args AS $param => $value) { + $stmt->bindParam($param, $args[$param]); + } + + if (!$stmt->execute()) { + $errorInfo = self::$dbo->db->errorInfo(); + self::$dbo->error = $errorInfo[2]; + self::$dbo->errorno = $errorInfo[1]; + $retval = false; + } else { + $retval = $stmt; + } + break; + case 'mysqli': + $stmt = self::$dbo->db->stmt_init(); + + if (!$stmt->prepare($sql)) { + self::$dbo->error = self::$dbo->db->error; + self::$dbo->errorno = self::$dbo->db->errno; + $retval = false; + break; + } + + $params = ''; + $values = array(); + foreach ($args AS $param => $value) { + if (is_int($args[$param])) { + $params .= 'i'; + } elseif (is_float($args[$param])) { + $params .= 'd'; + } elseif (is_string($args[$param])) { + $params .= 's'; + } else { + $params .= 'b'; + } + $values[] = &$args[$param]; + } + + array_unshift($values, $params); + + call_user_func_array(array($stmt, 'bind_param'), $values); + + if (!$stmt->execute()) { + self::$dbo->error = self::$dbo->db->error; + self::$dbo->errorno = self::$dbo->db->errno; + $retval = false; + } else { + $stmt->store_result(); + $retval = $stmt; + } + break; + case 'mysql': + // For the old "mysql" functions we cannot use prepared statements + $retval = mysql_query(self::replace_parameters($sql, $args), self::$dbo->db); + if (mysql_errno(self::$dbo->db)) { + self::$dbo->error = mysql_error(self::$dbo->db); + self::$dbo->errorno = mysql_errno(self::$dbo->db); + } + break; + } + + $a->save_timestamp($stamp1, 'database'); + + if (x($a->config,'system') && x($a->config['system'], 'db_log')) { + + $stamp2 = microtime(true); + $duration = (float)($stamp2 - $stamp1); + + if (($duration > $a->config["system"]["db_loglimit"])) { + $duration = round($duration, 3); + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + @file_put_contents($a->config["system"]["db_log"], datetime_convert()."\t".$duration."\t". + basename($backtrace[1]["file"])."\t". + $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t". + substr(self::replace_parameters($sql, $args), 0, 2000)."\n", FILE_APPEND); + } + } + return $retval; + } + + /** + * @brief Executes a prepared statement like UPDATE or INSERT that doesn't return data + * + * @param string $sql SQL statement + * @return boolean Was the query successfull? False is returned only if an error occurred + */ + static public function e($sql) { + $a = get_app(); + + $stamp = microtime(true); + + $args = func_get_args(); + + $stmt = call_user_func_array('self::p', $args); + + if (is_bool($stmt)) { + $retval = $stmt; + } elseif (is_object($stmt)) { + $retval = true; + } else { + $retval = false; + } + + self::close($stmt); + + $a->save_timestamp($stamp, "database_write"); + + return $retval; + } + + /** + * @brief Check if data exists + * + * @param string $sql SQL statement + * @return boolean Are there rows for that query? + */ + static public function exists($sql) { + $args = func_get_args(); + + $stmt = call_user_func_array('self::p', $args); + + if (is_bool($stmt)) { + $retval = $stmt; + } else { + $retval = (self::rows($stmt) > 0); + } + + self::close($stmt); + + return $retval; + } + + /** + * @brief Returns the number of rows of a statement + * + * @param object Statement object + * @return int Number of rows + */ + static public function num_rows($stmt) { + switch (self::$dbo->driver) { + case 'pdo': + return $stmt->rowCount(); + case 'mysqli': + return $stmt->num_rows; + case 'mysql': + return mysql_num_rows($stmt); + } + return 0; + } + + /** + * @brief Fetch a single row + * + * @param object $stmt statement object + * @return array current row + */ + static public function fetch($stmt) { + if (!is_object($stmt)) { + return false; + } + + switch (self::$dbo->driver) { + case 'pdo': + return $stmt->fetch(PDO::FETCH_ASSOC); + case 'mysqli': + // This code works, but is slow + + // Bind the result to a result array + $cols = array(); + + $cols_num = array(); + for ($x = 0; $x < $stmt->field_count; $x++) { + $cols[] = &$cols_num[$x]; + } + + call_user_func_array(array($stmt, 'bind_result'), $cols); + + if (!$stmt->fetch()) { + return false; + } + + // The slow part: + // We need to get the field names for the array keys + // It seems that there is no better way to do this. + $result = $stmt->result_metadata(); + $fields = $result->fetch_fields(); + + $columns = array(); + foreach ($cols_num AS $param => $col) { + $columns[$fields[$param]->name] = $col; + } + return $columns; + case 'mysql': + return mysql_fetch_array(self::$dbo->result, MYSQL_ASSOC); + } + } + + /** + * @brief Closes the current statement + * + * @param object $stmt statement object + * @return boolean was the close successfull? + */ + static public function close($stmt) { + if (!is_object($stmt)) { + return false; + } + + switch (self::$dbo->driver) { + case 'pdo': + return $stmt->closeCursor(); + case 'mysqli': + return $stmt->free_result(); + return $stmt->close(); + case 'mysql': + return mysql_free_result($stmt); + } + } } function printable($s) { diff --git a/include/dbclean.php b/include/dbclean.php index bff4ff2a24..64ceb51d37 100644 --- a/include/dbclean.php +++ b/include/dbclean.php @@ -43,96 +43,103 @@ function remove_orphans($stage = 0) { if (($stage == 1) OR ($stage == 0)) { logger("Deleting old global item entries from item table without user copy"); - if ($db->q("SELECT `id` FROM `item` WHERE `uid` = 0 + $r = dba::p("SELECT `id` FROM `item` WHERE `uid` = 0 AND NOT EXISTS (SELECT `guid` FROM `item` AS `i` WHERE `item`.`guid` = `i`.`guid` AND `i`.`uid` != 0) - AND `received` < UTC_TIMESTAMP() - INTERVAL 90 DAY LIMIT ".intval($limit), true)) { - $count = $db->num_rows(); + AND `received` < UTC_TIMESTAMP() - INTERVAL 90 DAY LIMIT ".intval($limit)); + $count = dba::num_rows($r); + if ($count > 0) { logger("found global item orphans: ".$count); - while ($orphan = $db->qfetch()) { + while ($orphan = dba::fetch($r)) { q("DELETE FROM `item` WHERE `id` = %d", intval($orphan["id"])); } } - $db->qclose(); + dba::close($r); logger("Done deleting old global item entries from item table without user copy"); } if (($stage == 2) OR ($stage == 0)) { logger("Deleting items without parents"); - if ($db->q("SELECT `id` FROM `item` WHERE NOT EXISTS (SELECT `id` FROM `item` AS `i` WHERE `item`.`parent` = `i`.`id`) LIMIT ".intval($limit), true)) { - $count = $db->num_rows(); + $r = dba::p("SELECT `id` FROM `item` WHERE NOT EXISTS (SELECT `id` FROM `item` AS `i` WHERE `item`.`parent` = `i`.`id`) LIMIT ".intval($limit)); + $count = dba::num_rows($r); + if ($count > 0) { logger("found item orphans without parents: ".$count); - while ($orphan = $db->qfetch()) { + while ($orphan = dba::fetch($r)) { q("DELETE FROM `item` WHERE `id` = %d", intval($orphan["id"])); } } - $db->qclose(); + dba::close($r); logger("Done deleting items without parents"); } if (($stage == 3) OR ($stage == 0)) { logger("Deleting orphaned data from thread table"); - if ($db->q("SELECT `iid` FROM `thread` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`parent` = `thread`.`iid`) LIMIT ".intval($limit), true)) { - $count = $db->num_rows(); + $r = dba::p("SELECT `iid` FROM `thread` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`parent` = `thread`.`iid`) LIMIT ".intval($limit)); + $count = dba::num_rows($r); + if ($count > 0) { logger("found thread orphans: ".$count); - while ($orphan = $db->qfetch()) { + while ($orphan = dba::fetch($r)) { q("DELETE FROM `thread` WHERE `iid` = %d", intval($orphan["iid"])); } } - $db->qclose(); + dba::close($r); logger("Done deleting orphaned data from thread table"); } if (($stage == 4) OR ($stage == 0)) { logger("Deleting orphaned data from notify table"); - if ($db->q("SELECT `iid` FROM `notify` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `notify`.`iid`) LIMIT ".intval($limit), true)) { - $count = $db->num_rows(); + $r = dba::p("SELECT `iid` FROM `notify` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `notify`.`iid`) LIMIT ".intval($limit)); + $count = dba::num_rows($r); + if ($count > 0) { logger("found notify orphans: ".$count); - while ($orphan = $db->qfetch()) { + while ($orphan = dba::fetch($r)) { q("DELETE FROM `notify` WHERE `iid` = %d", intval($orphan["iid"])); } } - $db->qclose(); + dba::close($r); logger("Done deleting orphaned data from notify table"); } if (($stage == 5) OR ($stage == 0)) { logger("Deleting orphaned data from notify-threads table"); - if ($db->q("SELECT `id` FROM `notify-threads` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`parent` = `notify-threads`.`master-parent-item`) LIMIT ".intval($limit), true)) { - $count = $db->num_rows(); + $r = dba::p("SELECT `id` FROM `notify-threads` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`parent` = `notify-threads`.`master-parent-item`) LIMIT ".intval($limit)); + $count = dba::num_rows($r); + if ($count > 0) { logger("found notify-threads orphans: ".$count); - while ($orphan = $db->qfetch()) { + while ($orphan = dba::fetch($r)) { q("DELETE FROM `notify-threads` WHERE `id` = %d", intval($orphan["id"])); } } - $db->qclose(); + dba::close($r); logger("Done deleting orphaned data from notify-threads table"); } if (($stage == 6) OR ($stage == 0)) { logger("Deleting orphaned data from sign table"); - if ($db->q("SELECT `iid` FROM `sign` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `sign`.`iid`) LIMIT ".intval($limit), true)) { - $count = $db->num_rows(); + $r = dba::p("SELECT `iid` FROM `sign` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `sign`.`iid`) LIMIT ".intval($limit)); + $count = dba::num_rows($r); + if ($count > 0) { logger("found sign orphans: ".$count); - while ($orphan = $db->qfetch()) { + while ($orphan = dba::fetch($r)) { q("DELETE FROM `sign` WHERE `iid` = %d", intval($orphan["iid"])); } } - $db->qclose(); + dba::close($r); logger("Done deleting orphaned data from sign table"); } if (($stage == 7) OR ($stage == 0)) { logger("Deleting orphaned data from term table"); - if ($db->q("SELECT `oid` FROM `term` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `term`.`oid`) LIMIT ".intval($limit), true)) { - $count = $db->num_rows(); + $r = dba::p("SELECT `oid` FROM `term` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `term`.`oid`) LIMIT ".intval($limit)); + $count = dba::num_rows($r); + if ($count > 0) { logger("found term orphans: ".$count); - while ($orphan = $db->qfetch()) { + while ($orphan = dba::fetch($r)) { q("DELETE FROM `term` WHERE `oid` = %d", intval($orphan["oid"])); } } - $db->qclose(); + dba::close($r); logger("Done deleting orphaned data from term table"); } diff --git a/include/dbm.php b/include/dbm.php index d28d49d63b..3430577da6 100644 --- a/include/dbm.php +++ b/include/dbm.php @@ -2,6 +2,7 @@ /** * @brief This class contain functions for the database management * + * This class contains functions that doesn't need to know if pdo, mysqli or whatever is used. */ class dbm { /** @@ -47,6 +48,11 @@ class dbm { if (is_bool($array)) { return $array; } + + if (is_object($array)) { + return true; + } + return (is_array($array) && count($array) > 0); } diff --git a/include/tags.php b/include/tags.php index 0a09438478..d809da2bf5 100644 --- a/include/tags.php +++ b/include/tags.php @@ -111,12 +111,11 @@ function create_tags_from_itemuri($itemuri, $uid) { } function update_items() { - global $db; - $messages = $db->q("SELECT `oid`,`item`.`guid`, `item`.`created`, `item`.`received` FROM `term` INNER JOIN `item` ON `item`.`id`=`term`.`oid` WHERE `term`.`otype` = 1 AND `term`.`guid` = ''", true); + $messages = dba::p("SELECT `oid`,`item`.`guid`, `item`.`created`, `item`.`received` FROM `term` INNER JOIN `item` ON `item`.`id`=`term`.`oid` WHERE `term`.`otype` = 1 AND `term`.`guid` = ''"); - logger("fetched messages: ".count($messages)); - while ($message = $db->qfetch()) { + logger("fetched messages: ".dba::num_rows($messages)); + while ($message = dba::fetch($messages)) { if ($message["uid"] == 0) { $global = true; @@ -135,15 +134,15 @@ function update_items() { intval($global), intval(TERM_OBJ_POST), intval($message["oid"])); } - $db->qclose(); + dba::close($messages); - $messages = $db->q("SELECT `guid` FROM `item` WHERE `uid` = 0", true); + $messages = dba::p("SELECT `guid` FROM `item` WHERE `uid` = 0"); - logger("fetched messages: ".count($messages)); - while ($message = $db->qfetch()) { + logger("fetched messages: ".dba::num_rows($messages)); + while ($message = dba::fetch(messages)) { q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($message["guid"])); } - $db->qclose(); + dba::close($messages); } ?> diff --git a/include/threads.php b/include/threads.php index c214cf2644..3d0aa05c3c 100644 --- a/include/threads.php +++ b/include/threads.php @@ -251,19 +251,17 @@ function delete_thread($itemid, $itemuri = "") { } function update_threads() { - global $db; - logger("update_threads: start"); - $messages = $db->q("SELECT `id` FROM `item` WHERE `id` = `parent`", true); + $messages = dba::p("SELECT `id` FROM `item` WHERE `id` = `parent`"); - logger("update_threads: fetched messages: ".count($messages)); + logger("update_threads: fetched messages: ".dba::num_rows($messages)); - while ($message = $db->qfetch()) { + while ($message = dba::fetch($messages)) { add_thread($message["id"]); add_shadow_thread($message["id"]); } - $db->qclose(); + dba::close($messages); } function update_threads_mention() { @@ -283,18 +281,16 @@ function update_threads_mention() { function update_shadow_copy() { - global $db; - logger("start"); - $messages = $db->q(sprintf("SELECT `iid` FROM `thread` WHERE `uid` != 0 AND `network` IN ('', '%s', '%s', '%s') - AND `visible` AND NOT `deleted` AND NOT `moderated` AND NOT `private` ORDER BY `created`", - NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS), true); + $messages = dba::p("SELECT `iid` FROM `thread` WHERE `uid` != 0 AND `network` IN ('', ?, ?, ?) + AND `visible` AND NOT `deleted` AND NOT `moderated` AND NOT `private` ORDER BY `created`", + NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS); - logger("fetched messages: ".count($messages)); - while ($message = $db->qfetch()) + logger("fetched messages: ".dba::num_rows($messages)); + while ($message = dba::fetch($messages)) add_shadow_thread($message["iid"]); - $db->qclose(); + dba::close($messages); } ?>