Commit eacb2bd1 authored by sam marshall's avatar sam marshall
Browse files

MDL-55356 core_search: API to queue contexts for indexing

New API \core_search\manager::request_index($context, $areaid = '')
adds the given context to a list which is intended to be indexed
later by the scheduled task.
parent d761b3fb
......@@ -3720,5 +3720,19 @@
<INDEX NAME="predictionidanduseridandactionname" UNIQUE="false" FIELDS="predictionid, userid, actionname"/>
<TABLE NAME="search_index_requests" COMMENT="Records requests for (re)indexing of specific contexts. Entries will be removed from this table when indexing of that context is complete. (This table is not used for normal time-based indexing of new content.)">
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Context ID that has been requested for reindexing."/>
<FIELD NAME="searcharea" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Set (e.g. 'forum-post') if a specific area is to be reindexed. Blank indicates all areas."/>
<FIELD NAME="timerequested" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Time at which this index update was requested."/>
<FIELD NAME="partialarea" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="If processing of this context partially completed, set to the area that needs processing next. Blank indicates not processed yet."/>
<FIELD NAME="partialtime" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="If processing partially completed, set to the timestamp within the next area where processing should start. 0 indicates not processed yet."/>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
\ No newline at end of file
......@@ -2601,5 +2601,31 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2017092900.00);
if ($oldversion < 2017100600.01) {
// Define table search_index_requests to be created.
$table = new xmldb_table('search_index_requests');
// Adding fields to table search_index_requests.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('searcharea', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('timerequested', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('partialarea', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('partialtime', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table search_index_requests.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('contextid', XMLDB_KEY_FOREIGN, array('contextid'), 'context', array('id'));
// Conditionally launch create table for search_index_requests.
if (!$dbman->table_exists($table)) {
// Main savepoint reached.
upgrade_main_savepoint(true, 2017100600.01);
return true;
......@@ -637,6 +637,8 @@ class manager {
* @return bool Whether there was any updated document or not.
public function index($fullindex = false, $timelimit = 0, \progress_trace $progress = null) {
global $DB;
// Cannot combine time limit with reindex.
if ($timelimit && $fullindex) {
throw new \coding_exception('Cannot apply time limit when reindexing');
......@@ -687,6 +689,10 @@ class manager {
if ($fullindex === true) {
$referencestarttime = 0;
// For full index, we delete any queued context index requests, as those will
// obviously be met by the full index.
} else {
$partial = get_config($componentconfigname, $varname . '_partial');
if ($partial) {
......@@ -905,4 +911,36 @@ class manager {
return false;
* Requests that a specific context is indexed by the scheduled task. The context will be
* added to a queue which is processed by the task.
* This is used after a restore to ensure that restored items are indexed, even though their
* modified time will be older than the latest indexed.
* @param \context $context Context to index within
* @param string $areaid Area to index, '' = all areas
public static function request_index(\context $context, $areaid = '') {
global $DB;
// Check through existing requests for this context or any parent context.
list ($contextsql, $contextparams) = $DB->get_in_or_equal(
$existing = $DB->get_records_select('search_index_requests',
'contextid ' . $contextsql, $contextparams, '', 'id, searcharea, partialarea');
foreach ($existing as $rec) {
// If we haven't started processing the existing request yet, and it covers the same
// area (or all areas) then that will be sufficient so don't add anything else.
if ($rec->partialarea === '' && ($rec->searcharea === $areaid || $rec->searcharea === '')) {
// No suitable existing request, so add a new one.
$newrecord = [ 'contextid' => $context->id, 'searcharea' => $areaid,
'timerequested' => time(), 'partialarea' => '', 'partialtime' => 0 ];
$DB->insert_record('search_index_requests', $newrecord);
......@@ -560,4 +560,82 @@ class search_manager_testcase extends advanced_testcase {
* Tests the request_index function used for reindexing certain contexts. This only tests
* adding things to the request list, it doesn't test that they are actually indexed by the
* scheduled task.
public function test_request_index() {
global $DB;
$course1 = $this->getDataGenerator()->create_course();
$course1ctx = context_course::instance($course1->id);
$course2 = $this->getDataGenerator()->create_course();
$course2ctx = context_course::instance($course2->id);
$forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course1->id]);
$forum1ctx = context_module::instance($forum1->cmid);
$forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course2->id]);
$forum2ctx = context_module::instance($forum2->cmid);
// Initially no requests.
$this->assertEquals(0, $DB->count_records('search_index_requests'));
// Request update for course 1, all areas.
// Check all details of entry.
$results = array_values($DB->get_records('search_index_requests'));
$this->assertCount(1, $results);
$this->assertEquals($course1ctx->id, $results[0]->contextid);
$this->assertEquals('', $results[0]->searcharea);
$now = time();
$this->assertLessThanOrEqual($now, $results[0]->timerequested);
$this->assertGreaterThan($now - 10, $results[0]->timerequested);
$this->assertEquals('', $results[0]->partialarea);
$this->assertEquals(0, $results[0]->partialtime);
// Request forum 1, all areas; not added as covered by course 1.
$this->assertEquals(1, $DB->count_records('search_index_requests'));
// Request forum 1, specific area; not added as covered by course 1 all areas.
\core_search\manager::request_index($forum1ctx, 'forum-post');
$this->assertEquals(1, $DB->count_records('search_index_requests'));
// Request course 1 again, specific area; not added as covered by all areas.
\core_search\manager::request_index($course1ctx, 'forum-post');
$this->assertEquals(1, $DB->count_records('search_index_requests'));
// Request course 1 again, all areas; not needed as covered already.
$this->assertEquals(1, $DB->count_records('search_index_requests'));
// Request course 2, specific area.
\core_search\manager::request_index($course2ctx, 'label-activity');
// Note: I'm ordering by ID for convenience - this is dangerous in real code (see MDL-43447)
// but in a unit test it shouldn't matter as nobody is using clustered databases for unit
// test.
$results = array_values($DB->get_records('search_index_requests', null, 'id'));
$this->assertCount(2, $results);
$this->assertEquals($course1ctx->id, $results[0]->contextid);
$this->assertEquals($course2ctx->id, $results[1]->contextid);
$this->assertEquals('label-activity', $results[1]->searcharea);
// Request forum 2, same specific area; not added.
\core_search\manager::request_index($forum2ctx, 'label-activity');
$this->assertEquals(2, $DB->count_records('search_index_requests'));
// Request forum 2, different specific area; added.
\core_search\manager::request_index($forum2ctx, 'forum-post');
$this->assertEquals(3, $DB->count_records('search_index_requests'));
// Request forum 2, all areas; also added. (Note: This could obviously remove the previous
// one, but for simplicity, I didn't make it do that; also it could perhaps cause problems
// if we had already begun processing the previous entry.)
$this->assertEquals(4, $DB->count_records('search_index_requests'));
......@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2017100600.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2017100600.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment