From 4e99fe48bcd89c8e3fcc6b81d1d6045dddf84fb6 Mon Sep 17 00:00:00 2001 From: Mark Sharp Date: Fri, 7 Oct 2022 15:37:39 +1100 Subject: [PATCH] MDL-72533 core_calendar: improve calendar event table peformance - Filter searchable courses if set. - Sites with large groups should now experience better performance. - Applies and optimises query against a proper date range. Co-authored-by: Mark Sharp Co-authored-by: Peter Sistrom Co-authored-by: Kevin Pham --- .../raw_event_retrieval_strategy.php | 76 +++++++++++++++++-- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/calendar/classes/local/event/strategies/raw_event_retrieval_strategy.php b/calendar/classes/local/event/strategies/raw_event_retrieval_strategy.php index 8e632ff4ce7..183158a0dd2 100644 --- a/calendar/classes/local/event/strategies/raw_event_retrieval_strategy.php +++ b/calendar/classes/local/event/strategies/raw_event_retrieval_strategy.php @@ -179,6 +179,9 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter $whereclause = implode(' AND ', $whereconditions); // Build SQL subquery and conditions for filtered events based on priorities. + $subquerytimeconditions = array_filter($whereconditions, function($condition) { + return (strpos($condition, 'time') !== false); + }); $subquerywhere = ''; $subqueryconditions = []; $subqueryparams = []; @@ -194,7 +197,12 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter // Set calendar filters. list($usercourses, $usergroups, $user) = calendar_set_filters($usercourses, true, $userrecord); - $allusercourses = array_merge($allusercourses, $usercourses); + $filteredcourses = is_array($courses) ? $courses : [$courses]; + $filteredcourses = array_filter($usercourses, function($course) use ($filteredcourses) { + return in_array($course, $filteredcourses); + }); + + $allusercourses = array_merge($allusercourses, $filteredcourses); // Flag to indicate whether the query needs to exclude group overrides. $viewgroupsonly = false; @@ -203,7 +211,8 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter // Set filter condition for the user's events. // Even though $user is a single scalar, we still use get_in_or_equal() because we are inside a loop. list($inusers, $inuserparams) = $DB->get_in_or_equal($user, SQL_PARAMS_NAMED); - $subqueryconditions[] = "(ev.userid $inusers AND ev.courseid = 0 AND ev.groupid = 0 AND ev.categoryid = 0)"; + $condition = "(ev.userid $inusers AND ev.courseid = 0 AND ev.groupid = 0 AND ev.categoryid = 0)"; + $subqueryconditions[] = $condition; $subqueryparams = array_merge($subqueryparams, $inuserparams); foreach ($usercourses as $courseid) { @@ -220,11 +229,11 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter // Set filter condition for the user's group events. if ($usergroups === true || $viewgroupsonly) { // Fetch group events, but not group overrides. - $subqueryconditions[] = "(ev.groupid != 0 AND ev.eventtype = 'group')"; + $groupconditions = "(ev.groupid != 0 AND ev.eventtype = 'group')"; } else if (!empty($usergroups)) { // Fetch group events and group overrides. list($inusergroups, $inusergroupparams) = $DB->get_in_or_equal($usergroups, SQL_PARAMS_NAMED); - $subqueryconditions[] = "(ev.groupid $inusergroups)"; + $groupconditions = "(ev.groupid $inusergroups)"; $subqueryparams = array_merge($subqueryparams, $inusergroupparams); } } @@ -263,8 +272,20 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter // Set subquery filter condition for the courses. if (!empty($subquerycourses)) { list($incourses, $incoursesparams) = $DB->get_in_or_equal($subquerycourses, SQL_PARAMS_NAMED); - $subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid $incourses AND ev.categoryid = 0)"; - $subqueryparams = array_merge($subqueryparams, $incoursesparams); + if (isset($groupconditions)) { + $groupconditions = $groupconditions." OR "; + } else { + $groupconditions = ''; + } + $condition = "($groupconditions(ev.groupid = 0 AND ev.courseid $incourses AND ev.categoryid = 0))"; + $subtimesparams = []; + if (!empty($subquerytimeconditions)) { + $subtimes = $this->subquerytimeconditions("courses", $subquerytimeconditions, $whereparams); + $condition .= $subtimes['where']; + $subtimesparams = $subtimes['params']; + } + $subqueryconditions[] = $condition; + $subqueryparams = array_merge($subqueryparams, $incoursesparams, $subtimesparams); } // Set subquery filter condition for the categories. @@ -272,8 +293,15 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter $subqueryconditions[] = "(ev.categoryid != 0 AND ev.eventtype = 'category')"; } else if (!empty($categories)) { list($incategories, $incategoriesparams) = $DB->get_in_or_equal($categories, SQL_PARAMS_NAMED); - $subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid = 0 AND ev.categoryid $incategories)"; - $subqueryparams = array_merge($subqueryparams, $incategoriesparams); + $condition = "(ev.groupid = 0 AND ev.courseid = 0 AND ev.categoryid $incategories)"; + $subtimesparams = []; + if (!empty($subquerytimeconditions)) { + $subtimes = $this->subquerytimeconditions("cats", $subquerytimeconditions, $whereparams); + $condition .= $subtimes['where']; + $subtimesparams = $subtimes['params']; + } + $subqueryconditions[] = $condition; + $subqueryparams = array_merge($subqueryparams, $incategoriesparams, $subtimesparams); } // Build the WHERE condition for the sub-query. @@ -322,4 +350,36 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter return $events === false ? [] : $events; } + + /** + * Returns a query fragment and params, with time constraints applied + * + * @param string $prefix + * @param array $conditions + * @param array $params + * @return array [, ] + */ + protected function subquerytimeconditions(string $prefix, array $conditions, array $params): array { + $outwhere = ''; + $outparams = []; + // Most specific to least specific. + $timeparams = ['timefromid', 'timefrom3', 'timefrom2', 'timefrom1', 'timefrom', 'timetoid', 'timeto2', 'timeto1', 'timeto']; + $whereconditions = []; + foreach ($conditions as $condition) { + $where = $condition; + // This query has been borrowed from the main WHERE clause, so the alias needs to be renamed to match the union. + $where = str_replace('e.id', 'ev.id', $where); + foreach ($timeparams as $timeparam) { + if (isset($params[$timeparam])) { + $where = str_replace(":{$timeparam}", ":{$prefix}{$timeparam}", $where); + $outparams["{$prefix}{$timeparam}"] = $params[$timeparam]; + } + } + $whereconditions[] = $where; + } + if (count($whereconditions) > 0) { + $outwhere = ' AND ' . implode(' AND ', $whereconditions); + } + return ['where' => $outwhere, 'params' => $outparams]; + } }