Elgg  Version 1.10
Database.php
Go to the documentation of this file.
1 <?php
2 namespace Elgg;
3 
14 class Database {
15 
17  private $tablePrefix;
18 
20  private $dbLinks = array();
21 
23  private $queryCount = 0;
24 
36  private $queryCache = null;
37 
41  private $queryCacheSize = 50;
42 
58  private $delayedQueries = array();
59 
61  private $installed = false;
62 
64  private $config;
65 
67  private $logger;
68 
75  public function __construct(\Elgg\Database\Config $config, \Elgg\Logger $logger) {
76 
77  $this->logger = $logger;
78  $this->config = $config;
79 
80  $this->tablePrefix = $config->getTablePrefix();
81 
82  $this->enableQueryCache();
83  }
84 
97  public function getLink($type) {
98  if (isset($this->dbLinks[$type])) {
99  return $this->dbLinks[$type];
100  } else if (isset($this->dbLinks['readwrite'])) {
101  return $this->dbLinks['readwrite'];
102  } else {
103  $this->setupConnections();
104  return $this->getLink($type);
105  }
106  }
107 
117  public function setupConnections() {
118  if ($this->config->isDatabaseSplit()) {
119  $this->establishLink('read');
120  $this->establishLink('write');
121  } else {
122  $this->establishLink('readwrite');
123  }
124  }
125 
126 
138  public function establishLink($dblinkname = "readwrite") {
139 
140  $conf = $this->config->getConnectionConfig($dblinkname);
141 
142  // Connect to database
143  $this->dbLinks[$dblinkname] = mysql_connect($conf['host'], $conf['user'], $conf['password'], true);
144  if (!$this->dbLinks[$dblinkname]) {
145  $msg = "Elgg couldn't connect to the database using the given credentials. Check the settings file.";
146  throw new \DatabaseException($msg);
147  }
148 
149  if (!mysql_select_db($conf['database'], $this->dbLinks[$dblinkname])) {
150  $msg = "Elgg couldn't select the database '{$conf['database']}'. Please check that the database is created and you have access to it.";
151  throw new \DatabaseException($msg);
152  }
153 
154  // Set DB for UTF8
155  mysql_query("SET NAMES utf8");
156  }
157 
174  public function getData($query, $callback = '') {
175  return $this->getResults($query, $callback, false);
176  }
177 
191  public function getDataRow($query, $callback = '') {
192  return $this->getResults($query, $callback, true);
193  }
194 
206  public function insertData($query) {
207 
208  $this->logger->log("DB query $query", \Elgg\Logger::INFO);
209 
210  $dblink = $this->getLink('write');
211 
212  $this->invalidateQueryCache();
213 
214  if ($this->executeQuery("$query", $dblink)) {
215  return mysql_insert_id($dblink);
216  }
217 
218  return false;
219  }
220 
232  public function updateData($query, $getNumRows = false) {
233 
234  $this->logger->log("DB query $query", \Elgg\Logger::INFO);
235 
236  $dblink = $this->getLink('write');
237 
238  $this->invalidateQueryCache();
239 
240  if ($this->executeQuery("$query", $dblink)) {
241  if ($getNumRows) {
242  return mysql_affected_rows($dblink);
243  } else {
244  return true;
245  }
246  }
247 
248  return false;
249  }
250 
261  public function deleteData($query) {
262 
263  $this->logger->log("DB query $query", \Elgg\Logger::INFO);
264 
265  $dblink = $this->getLink('write');
266 
267  $this->invalidateQueryCache();
268 
269  if ($this->executeQuery("$query", $dblink)) {
270  return mysql_affected_rows($dblink);
271  }
272 
273  return false;
274  }
275 
289  public function fingerprintCallback($callback) {
290  if (is_string($callback)) {
291  return $callback;
292  }
293  if (is_object($callback)) {
294  return spl_object_hash($callback) . "::__invoke";
295  }
296  if (is_array($callback)) {
297  if (is_string($callback[0])) {
298  return "{$callback[0]}::{$callback[1]}";
299  }
300  return spl_object_hash($callback[0]) . "::{$callback[1]}";
301  }
302  // this should not happen
303  return "";
304  }
305 
318  protected function getResults($query, $callback = null, $single = false) {
319 
320  // Since we want to cache results of running the callback, we need to
321  // need to namespace the query with the callback and single result request.
322  // https://github.com/elgg/elgg/issues/4049
323  $query_id = (int)$single . $query . '|';
324  if ($callback) {
325  $is_callable = is_callable($callback);
326  if ($is_callable) {
327  $query_id .= $this->fingerprintCallback($callback);
328  } else {
329  // TODO do something about invalid callbacks
330  $callback = null;
331  }
332  } else {
333  $is_callable = false;
334  }
335  // MD5 yields smaller mem usage for cache and cleaner logs
336  $hash = md5($query_id);
337 
338  // Is cached?
339  if ($this->queryCache) {
340  if (isset($this->queryCache[$hash])) {
341  $this->logger->log("DB query $query results returned from cache (hash: $hash)", \Elgg\Logger::INFO);
342  return $this->queryCache[$hash];
343  }
344  }
345 
346  $dblink = $this->getLink('read');
347  $return = array();
348 
349  if ($result = $this->executeQuery("$query", $dblink)) {
350  while ($row = mysql_fetch_object($result)) {
351  if ($is_callable) {
352  $row = call_user_func($callback, $row);
353  }
354 
355  if ($single) {
356  $return = $row;
357  break;
358  } else {
359  $return[] = $row;
360  }
361  }
362  }
363 
364  if (empty($return)) {
365  $this->logger->log("DB query $query returned no results.", \Elgg\Logger::INFO);
366  }
367 
368  // Cache result
369  if ($this->queryCache) {
370  $this->queryCache[$hash] = $return;
371  $this->logger->log("DB query $query results cached (hash: $hash)", \Elgg\Logger::INFO);
372  }
373 
374  return $return;
375  }
376 
390  public function executeQuery($query, $dblink) {
391 
392  if ($query == null) {
393  throw new \DatabaseException("Query cannot be null");
394  }
395 
396  if (!is_resource($dblink)) {
397  throw new \DatabaseException("Connection to database was lost.");
398  }
399 
400  $this->queryCount++;
401 
402  $result = mysql_query($query, $dblink);
403 
404  if (mysql_errno($dblink)) {
405  throw new \DatabaseException(mysql_error($dblink) . "\n\n QUERY: $query");
406  }
407 
408  return $result;
409  }
410 
435  public function runSqlScript($scriptlocation) {
436  $script = file_get_contents($scriptlocation);
437  if ($script) {
438 
439  $errors = array();
440 
441  // Remove MySQL '-- ' and '# ' style comments
442  $script = preg_replace('/^(?:--|#) .*$/m', '', $script);
443 
444  // Statements must end with ; and a newline
445  $sql_statements = preg_split('/;[\n\r]+/', "$script\n");
446 
447  foreach ($sql_statements as $statement) {
448  $statement = trim($statement);
449  $statement = str_replace("prefix_", $this->tablePrefix, $statement);
450  if (!empty($statement)) {
451  try {
452  $this->updateData($statement);
453  } catch (\DatabaseException $e) {
454  $errors[] = $e->getMessage();
455  }
456  }
457  }
458  if (!empty($errors)) {
459  $errortxt = "";
460  foreach ($errors as $error) {
461  $errortxt .= " {$error};";
462  }
463 
464  $msg = "There were a number of issues: " . $errortxt;
465  throw new \DatabaseException($msg);
466  }
467  } else {
468  $msg = "Elgg couldn't find the requested database script at " . $scriptlocation . ".";
469  throw new \DatabaseException($msg);
470  }
471  }
472 
486  public function registerDelayedQuery($query, $type, $handler = "") {
487 
488  if (!is_resource($type) && $type != 'read' && $type != 'write') {
489  return false;
490  }
491 
492  // Construct delayed query
493  $delayed_query = array();
494  $delayed_query['q'] = $query;
495  $delayed_query['l'] = $type;
496  $delayed_query['h'] = $handler;
497 
498  $this->delayedQueries[] = $delayed_query;
499 
500  return true;
501  }
502 
503 
512  public function executeDelayedQueries() {
513 
514  foreach ($this->delayedQueries as $query_details) {
515  try {
516  $link = $query_details['l'];
517 
518  if ($link == 'read' || $link == 'write') {
519  $link = $this->getLink($link);
520  } elseif (!is_resource($link)) {
521  $msg = "Link for delayed query not valid resource or db_link type. Query: {$query_details['q']}";
522  $this->logger->log($msg, \Elgg\Logger::WARNING);
523  }
524 
525  $result = $this->executeQuery($query_details['q'], $link);
526 
527  if ((isset($query_details['h'])) && (is_callable($query_details['h']))) {
528  $query_details['h']($result);
529  }
530  } catch (\DatabaseException $e) {
531  // Suppress all exceptions since page already sent to requestor
532  $this->logger->log($e, \Elgg\Logger::ERROR);
533  }
534  }
535  }
536 
544  public function enableQueryCache() {
545  if ($this->config->isQueryCacheEnabled() && $this->queryCache === null) {
546  // @todo if we keep this cache, expose the size as a config parameter
547  $this->queryCache = new \Elgg\Cache\LRUCache($this->queryCacheSize);
548  }
549  }
550 
559  public function disableQueryCache() {
560  $this->queryCache = null;
561  }
562 
568  protected function invalidateQueryCache() {
569  if ($this->queryCache) {
570  $this->queryCache->clear();
571  $this->logger->log("Query cache invalidated", \Elgg\Logger::INFO);
572  }
573  }
574 
581  public function assertInstalled() {
582 
583  if ($this->installed) {
584  return;
585  }
586 
587  try {
588  $dblink = $this->getLink('read');
589  mysql_query("SELECT value FROM {$this->tablePrefix}datalists WHERE name = 'installed'", $dblink);
590  if (mysql_errno($dblink) > 0) {
591  throw new \DatabaseException();
592  }
593  } catch (\DatabaseException $e) {
594  throw new \InstallationException("Unable to handle this request. This site is not configured or the database is down.");
595  }
596 
597  $this->installed = true;
598  }
599 
605  public function getQueryCount() {
606  return $this->queryCount;
607  }
608 
614  public function getTablePrefix() {
615  return $this->tablePrefix;
616  }
617 
625  public function sanitizeInt($value, $signed = true) {
626  $value = (int) $value;
627 
628  if ($signed === false) {
629  if ($value < 0) {
630  $value = 0;
631  }
632  }
633 
634  return $value;
635  }
636 
643  public function sanitizeString($value) {
644  return mysql_real_escape_string($value);
645  }
646 }
647 
if(!$owner||!($owner instanceof ElggUser)||!$owner->canEdit()) $error
Definition: upload.php:14
getData($query, $callback= '')
Retrieve rows from the database.
Definition: Database.php:174
getQueryCount()
Get the number of queries made to the database.
Definition: Database.php:605
getDataRow($query, $callback= '')
Retrieve a single row from the database.
Definition: Database.php:191
getLink($type)
Gets (if required, also creates) a database link resource.
Definition: Database.php:97
invalidateQueryCache()
Invalidate the query cache.
Definition: Database.php:568
$e
Definition: metadata.php:12
$CONFIG installed
Is the site fully installed.
Definition: config.php:115
deleteData($query)
Delete data from the database.
Definition: Database.php:261
$value
Definition: longtext.php:29
$return
Definition: opendd.php:15
executeQuery($query, $dblink)
Execute a query.
Definition: Database.php:390
insertData($query)
Insert a row into the database.
Definition: Database.php:206
sanitizeInt($value, $signed=true)
Sanitizes an integer value for use in a query.
Definition: Database.php:625
Save menu items.
sanitizeString($value)
Sanitizes a string for use in a query.
Definition: Database.php:643
assertInstalled()
Test that the Elgg database is installed.
Definition: Database.php:581
$type
Definition: add.php:8
runSqlScript($scriptlocation)
Runs a full database script from disk.
Definition: Database.php:435
registerDelayedQuery($query, $type, $handler="")
Queue a query for execution upon shutdown.
Definition: Database.php:486
fingerprintCallback($callback)
Get a string that uniquely identifies a callback during the current request.
Definition: Database.php:289
__construct(\Elgg\Database\Config $config,\Elgg\Logger $logger)
Constructor.
Definition: Database.php:75
updateData($query, $getNumRows=false)
Update the database.
Definition: Database.php:232
disableQueryCache()
Disable the query cache.
Definition: Database.php:559
enableQueryCache()
Enable the query cache.
Definition: Database.php:544
getResults($query, $callback=null, $single=false)
Handles queries that return results, running the results through a an optional callback function...
Definition: Database.php:318
establishLink($dblinkname="readwrite")
Establish a connection to the database server.
Definition: Database.php:138
elgg ajax ERROR
Definition: ajax.js:33
$row
$handler
Definition: add.php:10
executeDelayedQueries()
Trigger all queries that were registered as "delayed" queries.
Definition: Database.php:512
setupConnections()
Establish database connections.
Definition: Database.php:117
getTablePrefix()
Get the prefix for Elgg&#39;s tables.
Definition: Database.php:614