Merge pull request #3389 from annando/1704-db-query

New database functions
This commit is contained in:
Hypolite Petovan 2017-04-25 13:02:43 -04:00 committed by GitHub
commit 53fbd7e7c3
5 changed files with 344 additions and 112 deletions

View file

@ -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 * 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. * 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 { class dba {
@ -21,6 +22,7 @@ class dba {
public $connected = false; public $connected = false;
public $error = false; public $error = false;
private $_server_info = ''; private $_server_info = '';
private static $dbo;
function __construct($server, $user, $pass, $db, $install = false) { function __construct($server, $user, $pass, $db, $install = false) {
$a = get_app(); $a = get_app();
@ -93,6 +95,8 @@ class dba {
} }
} }
$a->save_timestamp($stamp1, "network"); $a->save_timestamp($stamp1, "network");
self::$dbo = $this;
} }
/** /**
@ -131,30 +135,6 @@ class dba {
return $r[0]['db']; 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. * @brief Analyze a database query and log this if some conditions are met.
* *
@ -249,7 +229,7 @@ class dba {
break; break;
} }
$stamp2 = microtime(true); $stamp2 = microtime(true);
$duration = (float)($stamp2-$stamp1); $duration = (float)($stamp2 - $stamp1);
$a->save_timestamp($stamp1, "database"); $a->save_timestamp($stamp1, "database");
@ -379,41 +359,6 @@ class dba {
return($r); 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) { public function dbg($dbg) {
$this->debug = $dbg; $this->debug = $dbg;
} }
@ -497,6 +442,285 @@ class dba {
} }
return $sql; 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) { function printable($s) {

View file

@ -43,96 +43,103 @@ function remove_orphans($stage = 0) {
if (($stage == 1) OR ($stage == 0)) { if (($stage == 1) OR ($stage == 0)) {
logger("Deleting old global item entries from item table without user copy"); 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 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)) { AND `received` < UTC_TIMESTAMP() - INTERVAL 90 DAY LIMIT ".intval($limit));
$count = $db->num_rows(); $count = dba::num_rows($r);
if ($count > 0) {
logger("found global item orphans: ".$count); 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"])); 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"); logger("Done deleting old global item entries from item table without user copy");
} }
if (($stage == 2) OR ($stage == 0)) { if (($stage == 2) OR ($stage == 0)) {
logger("Deleting items without parents"); 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)) { $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 = $db->num_rows(); $count = dba::num_rows($r);
if ($count > 0) {
logger("found item orphans without parents: ".$count); 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"])); q("DELETE FROM `item` WHERE `id` = %d", intval($orphan["id"]));
} }
} }
$db->qclose(); dba::close($r);
logger("Done deleting items without parents"); logger("Done deleting items without parents");
} }
if (($stage == 3) OR ($stage == 0)) { if (($stage == 3) OR ($stage == 0)) {
logger("Deleting orphaned data from thread table"); 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)) { $r = dba::p("SELECT `iid` FROM `thread` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`parent` = `thread`.`iid`) LIMIT ".intval($limit));
$count = $db->num_rows(); $count = dba::num_rows($r);
if ($count > 0) {
logger("found thread orphans: ".$count); logger("found thread orphans: ".$count);
while ($orphan = $db->qfetch()) { while ($orphan = dba::fetch($r)) {
q("DELETE FROM `thread` WHERE `iid` = %d", intval($orphan["iid"])); q("DELETE FROM `thread` WHERE `iid` = %d", intval($orphan["iid"]));
} }
} }
$db->qclose(); dba::close($r);
logger("Done deleting orphaned data from thread table"); logger("Done deleting orphaned data from thread table");
} }
if (($stage == 4) OR ($stage == 0)) { if (($stage == 4) OR ($stage == 0)) {
logger("Deleting orphaned data from notify table"); 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)) { $r = dba::p("SELECT `iid` FROM `notify` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `notify`.`iid`) LIMIT ".intval($limit));
$count = $db->num_rows(); $count = dba::num_rows($r);
if ($count > 0) {
logger("found notify orphans: ".$count); logger("found notify orphans: ".$count);
while ($orphan = $db->qfetch()) { while ($orphan = dba::fetch($r)) {
q("DELETE FROM `notify` WHERE `iid` = %d", intval($orphan["iid"])); q("DELETE FROM `notify` WHERE `iid` = %d", intval($orphan["iid"]));
} }
} }
$db->qclose(); dba::close($r);
logger("Done deleting orphaned data from notify table"); logger("Done deleting orphaned data from notify table");
} }
if (($stage == 5) OR ($stage == 0)) { if (($stage == 5) OR ($stage == 0)) {
logger("Deleting orphaned data from notify-threads table"); 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)) { $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 = $db->num_rows(); $count = dba::num_rows($r);
if ($count > 0) {
logger("found notify-threads orphans: ".$count); 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"])); 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"); logger("Done deleting orphaned data from notify-threads table");
} }
if (($stage == 6) OR ($stage == 0)) { if (($stage == 6) OR ($stage == 0)) {
logger("Deleting orphaned data from sign table"); 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)) { $r = dba::p("SELECT `iid` FROM `sign` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `sign`.`iid`) LIMIT ".intval($limit));
$count = $db->num_rows(); $count = dba::num_rows($r);
if ($count > 0) {
logger("found sign orphans: ".$count); logger("found sign orphans: ".$count);
while ($orphan = $db->qfetch()) { while ($orphan = dba::fetch($r)) {
q("DELETE FROM `sign` WHERE `iid` = %d", intval($orphan["iid"])); q("DELETE FROM `sign` WHERE `iid` = %d", intval($orphan["iid"]));
} }
} }
$db->qclose(); dba::close($r);
logger("Done deleting orphaned data from sign table"); logger("Done deleting orphaned data from sign table");
} }
if (($stage == 7) OR ($stage == 0)) { if (($stage == 7) OR ($stage == 0)) {
logger("Deleting orphaned data from term table"); 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)) { $r = dba::p("SELECT `oid` FROM `term` WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `term`.`oid`) LIMIT ".intval($limit));
$count = $db->num_rows(); $count = dba::num_rows($r);
if ($count > 0) {
logger("found term orphans: ".$count); logger("found term orphans: ".$count);
while ($orphan = $db->qfetch()) { while ($orphan = dba::fetch($r)) {
q("DELETE FROM `term` WHERE `oid` = %d", intval($orphan["oid"])); q("DELETE FROM `term` WHERE `oid` = %d", intval($orphan["oid"]));
} }
} }
$db->qclose(); dba::close($r);
logger("Done deleting orphaned data from term table"); logger("Done deleting orphaned data from term table");
} }

View file

@ -2,6 +2,7 @@
/** /**
* @brief This class contain functions for the database management * @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 { class dbm {
/** /**
@ -47,6 +48,11 @@ class dbm {
if (is_bool($array)) { if (is_bool($array)) {
return $array; return $array;
} }
if (is_object($array)) {
return true;
}
return (is_array($array) && count($array) > 0); return (is_array($array) && count($array) > 0);
} }

View file

@ -111,12 +111,11 @@ function create_tags_from_itemuri($itemuri, $uid) {
} }
function update_items() { 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)); logger("fetched messages: ".dba::num_rows($messages));
while ($message = $db->qfetch()) { while ($message = dba::fetch($messages)) {
if ($message["uid"] == 0) { if ($message["uid"] == 0) {
$global = true; $global = true;
@ -135,15 +134,15 @@ function update_items() {
intval($global), intval(TERM_OBJ_POST), intval($message["oid"])); 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)); logger("fetched messages: ".dba::num_rows($messages));
while ($message = $db->qfetch()) { while ($message = dba::fetch(messages)) {
q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($message["guid"])); q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($message["guid"]));
} }
$db->qclose(); dba::close($messages);
} }
?> ?>

View file

@ -251,19 +251,17 @@ function delete_thread($itemid, $itemuri = "") {
} }
function update_threads() { function update_threads() {
global $db;
logger("update_threads: start"); 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_thread($message["id"]);
add_shadow_thread($message["id"]); add_shadow_thread($message["id"]);
} }
$db->qclose(); dba::close($messages);
} }
function update_threads_mention() { function update_threads_mention() {
@ -283,18 +281,16 @@ function update_threads_mention() {
function update_shadow_copy() { function update_shadow_copy() {
global $db;
logger("start"); logger("start");
$messages = $db->q(sprintf("SELECT `iid` FROM `thread` WHERE `uid` != 0 AND `network` IN ('', '%s', '%s', '%s') $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`", AND `visible` AND NOT `deleted` AND NOT `moderated` AND NOT `private` ORDER BY `created`",
NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS), true); NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS);
logger("fetched messages: ".count($messages)); logger("fetched messages: ".dba::num_rows($messages));
while ($message = $db->qfetch()) while ($message = dba::fetch($messages))
add_shadow_thread($message["iid"]); add_shadow_thread($message["iid"]);
$db->qclose(); dba::close($messages);
} }
?> ?>