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