diff options
Diffstat (limited to 'engine/lib/database.php')
| -rw-r--r-- | engine/lib/database.php | 228 |
1 files changed, 144 insertions, 84 deletions
diff --git a/engine/lib/database.php b/engine/lib/database.php index a9c4017a0..a7949788d 100644 --- a/engine/lib/database.php +++ b/engine/lib/database.php @@ -12,15 +12,19 @@ /** * Query cache for all queries. * - * Each query and its results are stored in this array as: + * Each query and its results are stored in this cache as: * <code> - * $DB_QUERY_CACHE[$query] => array(result1, result2, ... resultN) + * $DB_QUERY_CACHE[query hash] => array(result1, result2, ... resultN) * </code> + * @see elgg_query_runner() for details on the hash. * - * @global array $DB_QUERY_CACHE + * @warning Elgg used to set this as an empty array to turn off the cache + * + * @global ElggLRUCache|null $DB_QUERY_CACHE + * @access private */ global $DB_QUERY_CACHE; -$DB_QUERY_CACHE = array(); +$DB_QUERY_CACHE = null; /** * Queries to be executed upon shutdown. @@ -38,6 +42,7 @@ $DB_QUERY_CACHE = array(); * </code> * * @global array $DB_DELAYED_QUERIES + * @access private */ global $DB_DELAYED_QUERIES; $DB_DELAYED_QUERIES = array(); @@ -48,7 +53,8 @@ $DB_DELAYED_QUERIES = array(); * Each database link created with establish_db_link($name) is stored in * $dblink as $dblink[$name] => resource. Use get_db_link($name) to retrieve it. * - * @global array $dblink + * @global resource[] $dblink + * @access private */ global $dblink; $dblink = array(); @@ -59,6 +65,7 @@ $dblink = array(); * Each call to the database increments this counter. * * @global integer $dbcalls + * @access private */ global $dbcalls; $dbcalls = 0; @@ -72,10 +79,12 @@ $dbcalls = 0; * resource. eg "read", "write", or "readwrite". * * @return void + * @throws DatabaseException + * @access private */ function establish_db_link($dblinkname = "readwrite") { // Get configuration, and globalise database link - global $CONFIG, $dblink, $DB_QUERY_CACHE, $dbcalls; + global $CONFIG, $dblink, $DB_QUERY_CACHE; if ($dblinkname != "readwrite" && isset($CONFIG->db[$dblinkname])) { if (is_array($CONFIG->db[$dblinkname])) { @@ -119,7 +128,8 @@ function establish_db_link($dblinkname = "readwrite") { // Set up cache if global not initialized and query cache not turned off if ((!$DB_QUERY_CACHE) && (!$db_cache_off)) { - $DB_QUERY_CACHE = new ElggStaticVariableCache('db_query_cache'); + // @todo if we keep this cache in 1.9, expose the size as a config parameter + $DB_QUERY_CACHE = new ElggLRUCache(200); } } @@ -130,9 +140,10 @@ function establish_db_link($dblinkname = "readwrite") { * links up separately; otherwise just create the one database link. * * @return void + * @access private */ function setup_db_connections() { - global $CONFIG, $dblink; + global $CONFIG; if (!empty($CONFIG->db->split)) { establish_db_link('read'); @@ -146,6 +157,7 @@ function setup_db_connections() { * Display profiling information about db at NOTICE debug level upon shutdown. * * @return void + * @access private */ function db_profiling_shutdown_hook() { global $dbcalls; @@ -158,15 +170,23 @@ function db_profiling_shutdown_hook() { * Execute any delayed queries upon shutdown. * * @return void + * @access private */ function db_delayedexecution_shutdown_hook() { global $DB_DELAYED_QUERIES; foreach ($DB_DELAYED_QUERIES as $query_details) { - // use one of our db functions so it is included in profiling. - $result = execute_query($query_details['q'], $query_details['l']); - try { + $link = $query_details['l']; + + if ($link == 'read' || $link == 'write') { + $link = get_db_link($link); + } elseif (!is_resource($link)) { + elgg_log("Link for delayed query not valid resource or db_link type. Query: {$query_details['q']}", 'WARNING'); + } + + $result = execute_query($query_details['q'], $link); + if ((isset($query_details['h'])) && (is_callable($query_details['h']))) { $query_details['h']($result); } @@ -178,21 +198,6 @@ function db_delayedexecution_shutdown_hook() { } /** - * Registers shutdown functions for database profiling and delayed queries. - * - * @note Database connections are established upon first call to database. - * - * @return true - * @elgg_event_handler boot system - */ -function init_db() { - register_shutdown_function('db_delayedexecution_shutdown_hook'); - register_shutdown_function('db_profiling_shutdown_hook'); - - return true; -} - -/** * Returns (if required, also creates) a database link resource. * * Database link resources are stored in the {@link $dblink} global. These @@ -201,7 +206,8 @@ function init_db() { * * @param string $dblinktype The type of link we want: "read", "write" or "readwrite". * - * @return object Database link + * @return resource Database link + * @access private */ function get_db_link($dblinktype) { global $dblink; @@ -219,10 +225,11 @@ function get_db_link($dblinktype) { /** * Execute an EXPLAIN for $query. * - * @param str $query The query to explain + * @param string $query The query to explain * @param mixed $link The database link resource to user. * * @return mixed An object of the query's result, or FALSE + * @access private */ function explain_query($query, $link) { if ($result = execute_query("explain " . $query, $link)) { @@ -242,20 +249,23 @@ function explain_query($query, $link) { * {@link $dbcalls} is incremented and the query is saved into the {@link $DB_QUERY_CACHE}. * * @param string $query The query - * @param link $dblink The DB link + * @param resource $dblink The DB link * - * @return The result of mysql_query() + * @return resource result of mysql_query() * @throws DatabaseException + * @access private */ function execute_query($query, $dblink) { - global $CONFIG, $dbcalls; + global $dbcalls; - // remove newlines so logs are easier to read - $query = preg_replace("/[\r\n]/", "", $query); if ($query == NULL) { throw new DatabaseException(elgg_echo('DatabaseException:InvalidQuery')); } + if (!is_resource($dblink)) { + throw new DatabaseException(elgg_echo('DatabaseException:InvalidDBLink')); + } + $dbcalls++; $result = mysql_query($query, $dblink); @@ -274,10 +284,11 @@ function execute_query($query, $dblink) { * the raw result from {@link mysql_query()}. * * @param string $query The query to execute - * @param resource $dblink The database link to use + * @param resource|string $dblink The database link to use or the link type (read | write) * @param string $handler A callback function to pass the results array to * * @return true + * @access private */ function execute_delayed_query($query, $dblink, $handler = "") { global $DB_DELAYED_QUERIES; @@ -286,6 +297,10 @@ function execute_delayed_query($query, $dblink, $handler = "") { $DB_DELAYED_QUERIES = array(); } + if (!is_resource($dblink) && $dblink != 'read' && $dblink != 'write') { + return false; + } + // Construct delayed query $delayed_query = array(); $delayed_query['q'] = $query; @@ -306,9 +321,10 @@ function execute_delayed_query($query, $dblink, $handler = "") { * @return true * @uses execute_delayed_query() * @uses get_db_link() + * @access private */ function execute_delayed_write_query($query, $handler = "") { - return execute_delayed_query($query, get_db_link('write'), $handler); + return execute_delayed_query($query, 'write', $handler); } /** @@ -320,9 +336,10 @@ function execute_delayed_write_query($query, $handler = "") { * @return true * @uses execute_delayed_query() * @uses get_db_link() + * @access private */ function execute_delayed_read_query($query, $handler = "") { - return execute_delayed_query($query, get_db_link('read'), $handler); + return execute_delayed_query($query, 'read', $handler); } /** @@ -339,6 +356,7 @@ function execute_delayed_read_query($query, $handler = "") { * * @return array An array of database result objects or callback function results. If the query * returned nothing, an empty array. + * @access private */ function get_data($query, $callback = "") { return elgg_query_runner($query, $callback, false); @@ -355,6 +373,7 @@ function get_data($query, $callback = "") { * @param string $callback A callback function * * @return mixed A single database result object or the result of the callback function. + * @access private */ function get_data_row($query, $callback = "") { return elgg_query_runner($query, $callback, true); @@ -362,7 +381,7 @@ function get_data_row($query, $callback = "") { /** * Handles returning data from a query, running it through a callback function, - * and caching the results. + * and caching the results. This is for R queries (from CRUD). * * @access private * @@ -373,21 +392,21 @@ function get_data_row($query, $callback = "") { * @return array An array of database result objects or callback function results. If the query * returned nothing, an empty array. * @since 1.8.0 + * @access private */ function elgg_query_runner($query, $callback = null, $single = false) { - global $CONFIG, $DB_QUERY_CACHE; + global $DB_QUERY_CACHE; - // since we want to cache results of running the callback, we need to - // need to namespace the query with the callback, and single result request. - $hash = (string)$callback . (string)$single . $query; + // Since we want to cache results of running the callback, we need to + // need to namespace the query with the callback and single result request. + // https://github.com/elgg/elgg/issues/4049 + $hash = (string)$callback . (int)$single . $query; // Is cached? if ($DB_QUERY_CACHE) { - $cached_query = $DB_QUERY_CACHE[$hash]; - - if ($cached_query !== FALSE) { - elgg_log("$query results returned from cache (hash: $hash)"); - return $cached_query; + if (isset($DB_QUERY_CACHE[$hash])) { + elgg_log("DB query $query results returned from cache (hash: $hash)", 'NOTICE'); + return $DB_QUERY_CACHE[$hash]; } } @@ -398,7 +417,7 @@ function elgg_query_runner($query, $callback = null, $single = false) { // test for callback once instead of on each iteration. // @todo check profiling to see if this needs to be broken out into - // explicit cases instead of checking in the interation. + // explicit cases instead of checking in the iteration. $is_callable = is_callable($callback); while ($row = mysql_fetch_object($result)) { if ($is_callable) { @@ -415,13 +434,13 @@ function elgg_query_runner($query, $callback = null, $single = false) { } if (empty($return)) { - elgg_log("DB query \"$query\" returned no results."); + elgg_log("DB query $query returned no results.", 'NOTICE'); } // Cache result if ($DB_QUERY_CACHE) { $DB_QUERY_CACHE[$hash] = $return; - elgg_log("$query results cached (hash: $hash)"); + elgg_log("DB query $query results cached (hash: $hash)", 'NOTICE'); } return $return; @@ -436,18 +455,15 @@ function elgg_query_runner($query, $callback = null, $single = false) { * * @return int|false The database id of the inserted row if a AUTO_INCREMENT field is * defined, 0 if not, and false on failure. + * @access private */ function insert_data($query) { - global $CONFIG, $DB_QUERY_CACHE; + elgg_log("DB query $query", 'NOTICE'); + $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - } - - elgg_log("Query cache invalidated"); + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { return mysql_insert_id($dblink); @@ -457,24 +473,22 @@ function insert_data($query) { } /** - * Update a row in the database. + * Update the database. * * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}. * * @param string $query The query to run. * - * @return Bool + * @return bool + * @access private */ function update_data($query) { - global $CONFIG, $DB_QUERY_CACHE; + + elgg_log("DB query $query", 'NOTICE'); $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - elgg_log("Query cache invalidated"); - } + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { return TRUE; @@ -484,24 +498,22 @@ function update_data($query) { } /** - * Remove a row from the database. + * Remove data from the database. * * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}. * * @param string $query The SQL query to run * * @return int|false The number of affected rows or false on failure + * @access private */ function delete_data($query) { - global $CONFIG, $DB_QUERY_CACHE; + + elgg_log("DB query $query", 'NOTICE'); $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - elgg_log("Query cache invalidated"); - } + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { return mysql_affected_rows($dblink); @@ -510,6 +522,22 @@ function delete_data($query) { return FALSE; } +/** + * Invalidate the query cache + * + * @access private + */ +function _elgg_invalidate_query_cache() { + global $DB_QUERY_CACHE; + if ($DB_QUERY_CACHE instanceof ElggLRUCache) { + $DB_QUERY_CACHE->clear(); + elgg_log("Query cache invalidated", 'NOTICE'); + } elseif ($DB_QUERY_CACHE) { + // In case someone sets the cache to an array and primes it with data + $DB_QUERY_CACHE = array(); + elgg_log("Query cache invalidated", 'NOTICE'); + } +} /** * Return tables matching the database prefix {@link $CONFIG->dbprefix}% in the currently @@ -517,6 +545,7 @@ function delete_data($query) { * * @return array|false List of tables or false on failure * @static array $tables Tables found matching the database prefix + * @access private */ function get_db_tables() { global $CONFIG; @@ -559,6 +588,7 @@ function get_db_tables() { * @param string $table The name of the table to optimise * * @return bool + * @access private */ function optimize_table($table) { $table = sanitise_string($table); @@ -571,6 +601,7 @@ function optimize_table($table) { * @param resource $dblink The DB link * * @return string Database error message + * @access private */ function get_db_error($dblink) { return mysql_error($dblink); @@ -595,6 +626,7 @@ function get_db_error($dblink) { * * @return void * @throws DatabaseException + * @access private */ function run_sql_script($scriptlocation) { if ($script = file_get_contents($scriptlocation)) { @@ -613,7 +645,7 @@ function run_sql_script($scriptlocation) { $statement = str_replace("prefix_", $CONFIG->dbprefix, $statement); if (!empty($statement)) { try { - $result = update_data($statement); + update_data($statement); } catch (DatabaseException $e) { $errors[] = $e->getMessage(); } @@ -635,6 +667,18 @@ function run_sql_script($scriptlocation) { } /** + * Format a query string for logging + * + * @param string $query Query string + * @return string + * @access private + */ +function elgg_format_query($query) { + // remove newlines and extra spaces so logs are easier to read + return preg_replace('/\s\s+/', ' ', $query); +} + +/** * Sanitise a string for database use, but with the option of escaping extra characters. * * @param string $string The string to sanitise @@ -679,26 +723,42 @@ function sanitize_string($string) { /** * Sanitises an integer for database use. * - * @param int $int Integer - * - * @return int Sanitised integer + * @param int $int Value to be sanitized + * @param bool $signed Whether negative values should be allowed (true) + * @return int */ -function sanitise_int($int) { +function sanitise_int($int, $signed = true) { + $int = (int) $int; + + if ($signed === false) { + if ($int < 0) { + $int = 0; + } + } + return (int) $int; } /** - * Wrapper function for alternate English spelling - * - * @param int $int Integer + * Sanitizes an integer for database use. + * Wrapper function for alternate English spelling (@see sanitise_int) * - * @return int Sanitised integer + * @param int $int Value to be sanitized + * @param bool $signed Whether negative values should be allowed (true) + * @return int */ -function sanitize_int($int) { - return (int) $int; +function sanitize_int($int, $signed = true) { + return sanitise_int($int, $signed); } /** - * @elgg_register_event boot system init_db + * Registers shutdown functions for database profiling and delayed queries. + * + * @access private */ -elgg_register_event_handler('boot', 'system', 'init_db', 0); +function init_db() { + register_shutdown_function('db_delayedexecution_shutdown_hook'); + register_shutdown_function('db_profiling_shutdown_hook'); +} + +elgg_register_event_handler('init', 'system', 'init_db'); |
