Skip to content

Commit

Permalink
MDL-62619 privacy: Improve performance of contexts loading
Browse files Browse the repository at this point in the history
When possible, we want to use JOIN over WHERE IN. It is known that the
later performs badly in MySQL.
  • Loading branch information
andrewnicols authored and mudrd8mz committed Jul 27, 2018
1 parent 6e8235c commit d7cb755
Showing 1 changed file with 59 additions and 1 deletion.
60 changes: 59 additions & 1 deletion privacy/classes/local/request/contextlist.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,30 @@ public function add_from_sql(string $sql, array $params) : contextlist {
global $DB;

$fields = \context_helper::get_preload_record_columns_sql('ctx');
$wrapper = "SELECT {$fields} FROM {context} ctx WHERE id IN ({$sql})";
if ($fieldname = $this->guess_id_field_from_sql($sql)) {
if (is_int($fieldname)) {
$wrapper = "
SELECT
{$fields}
FROM {context} ctx
WHERE ctx.id = :fieldvalue";
$params = ['fieldvalue' => $fieldname];
} else {
// Able to guess a field name.
$wrapper = "
SELECT
{$fields}
FROM {context} ctx
JOIN ({$sql}) target ON ctx.id = target.{$fieldname}";
}
} else {
// No field name available. Fall back on a potentially slower version.
$wrapper = "
SELECT
{$fields}
FROM {context} ctx
WHERE ctx.id IN ({$sql})";
}
$contexts = $DB->get_recordset_sql($wrapper, $params);

$contextids = [];
Expand Down Expand Up @@ -110,4 +133,39 @@ public function add_user_contexts(array $userids) : contextlist {
public function set_component($component) {
parent::set_component($component);
}

/**
* Guess the name of the contextid field from the supplied SQL.
*
* @param string $sql The SQL to guess from
* @return string The field name.
*/
protected function guess_id_field_from_sql(string $sql) : string {
// Get the list of relevant words from the SQL Query.
// We explode the SQL by the space character, then trim any extra whitespace (e.g. newlines), before we filter
// empty value, and finally we re-index the array.
$words = array_map('trim', explode(' ', $sql));
$words = array_filter($words, function($word) {
return $word !== '';
});
$words = array_values($words);

if ($firstfrom = array_search('FROM', $words)) {
// Found a FROM keyword.
// Select the previous word.
$fieldname = $words[$firstfrom - 1];
if (is_int($fieldname)) {
return $fieldname;
}

if ($hasdot = strpos($fieldname, '.')) {
// This field is against a table alias. Take off the alias.
$fieldname = substr($fieldname, $hasdot + 1);
}

return $fieldname;
}

return '';
}
}

0 comments on commit d7cb755

Please sign in to comment.