Merge branch 'MDL-53758-master' of git://github.com/merrill-oakland/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 26 Apr 2016 18:34:20 +0000 (20:34 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 26 Apr 2016 18:34:20 +0000 (20:34 +0200)
1  2 
search/classes/engine.php
search/classes/manager.php
search/engine/solr/classes/engine.php
search/engine/solr/tests/engine_test.php
search/index.php

Simple merge
@@@ -407,16 -452,12 +462,17 @@@ class manager 
       * It might return the results from the cache instead.
       *
       * @param stdClass $formdata
+      * @param int      $limit The maximum number of documents to return
       * @return \core_search\document[]
       */
-     public function search(\stdClass $formdata) {
+     public function search(\stdClass $formdata, $limit = 0) {
          global $USER;
  
 +        $limitcourseids = false;
 +        if (!empty($formdata->courseids)) {
 +            $limitcourseids = $formdata->courseids;
 +        }
 +
          // Clears previous query errors.
          $this->engine->clear_query_error();
  
@@@ -111,14 -133,130 +133,126 @@@ class engine extends \core_search\engin
  
          // If there is any problem we trigger the exception as soon as possible.
          $client = $this->get_search_client();
 -        $serverstatus = $this->is_server_ready();
 -        if ($serverstatus !== true) {
 -            throw new \core_search\engine_exception('engineserverstatus', 'search');
 -        }
  
-         $query = new \SolrDisMaxQuery();
-         $maxrows = \core_search\manager::MAX_RESULTS;
-         if ($this->file_indexing_enabled()) {
-             // When using file indexing and grouping, we are going to collapse results, so we want extra results.
-             $maxrows *= 2;
+         // Create the query object.
+         $query = $this->create_user_query($filters, $usercontexts);
+         // We expect good match rates, so for our first get, we will get a small number of records.
+         // This significantly speeds solr response time for first few pages.
+         $query->setRows(min($limit * 3, static::QUERY_SIZE));
+         $response = $this->get_query_response($query);
+         // Get count data out of the response, and reset our counters.
+         list($included, $found) = $this->get_response_counts($response);
+         $this->totalenginedocs = $found;
+         $this->processeddocs = 0;
+         $this->skippeddocs = 0;
+         if ($included == 0 || $this->totalenginedocs == 0) {
+             // No results.
+             return array();
+         }
+         // Get valid documents out of the response.
+         $results = $this->process_response($response, $limit);
+         // We have processed all the docs in the response at this point.
+         $this->processeddocs += $included;
+         // If we haven't reached the limit, and there are more docs left in Solr, lets keep trying.
+         while (count($results) < $limit && ($this->totalenginedocs - $this->processeddocs) > 0) {
+             // Offset the start of the query, and since we are making another call, get more per call.
+             $query->setStart($this->processeddocs);
+             $query->setRows(static::QUERY_SIZE);
+             $response = $this->get_query_response($query);
+             list($included, $found) = $this->get_response_counts($response);
+             if ($included == 0 || $found == 0) {
+                 // No new results were found. Found being empty would be weird, so we will just return.
+                 return $results;
+             }
+             $this->totalenginedocs = $found;
+             // Get the new response docs, limiting to remaining we need, then add it to the end of the results array.
+             $newdocs = $this->process_response($response, $limit - count($results));
+             $results = array_merge($results, $newdocs);
+             // Add to our processed docs count.
+             $this->processeddocs += $included;
+         }
+         return $results;
+     }
+     /**
+      * Takes a query and returns the response in SolrObject format.
+      *
+      * @param  SolrQuery  $query Solr query object.
+      * @return SolrObject|false Response document or false on error.
+      */
+     protected function get_query_response($query) {
+         try {
+             return $this->get_search_client()->query($query)->getResponse();
+         } catch (\SolrClientException $ex) {
+             debugging('Error executing the provided query: ' . $ex->getMessage(), DEBUG_DEVELOPER);
+             $this->queryerror = $ex->getMessage();
+             return false;
+         } catch (\SolrServerException $ex) {
+             debugging('Error executing the provided query: ' . $ex->getMessage(), DEBUG_DEVELOPER);
+             $this->queryerror = $ex->getMessage();
+             return false;
+         }
+     }
+     /**
+      * Returns the total number of documents available for the most recently call to execute_query.
+      *
+      * @return int
+      */
+     public function get_query_total_count() {
+         // Return the total engine count minus the docs we have determined are bad.
+         return $this->totalenginedocs - $this->skippeddocs;
+     }
+     /**
+      * Returns count information for a provided response. Will return 0, 0 for invalid or empty responses.
+      *
+      * @param SolrDocument $response The response document from Solr.
+      * @return array A two part array. First how many response docs are in the response.
+      *               Second, how many results are vailable in the engine.
+      */
+     protected function get_response_counts($response) {
+         $found = 0;
+         $included = 0;
+         if (isset($response->grouped->solr_filegroupingid->ngroups)) {
+             // Get the number of results for file grouped queries.
+             $found = $response->grouped->solr_filegroupingid->ngroups;
+             $included = count($response->grouped->solr_filegroupingid->groups);
+         } else if (isset($response->response->numFound)) {
+             // Get the number of results for standard queries.
+             $found = $response->response->numFound;
+             $included = count($response->response->docs);
          }
-         $this->set_query($query, $data->q, $maxrows);
+         return array($included, $found);
+     }
+     /**
+      * Prepares a new query object with needed limits, filters, etc.
+      *
+      * @param stdClass  $filters Containing query and filters.
+      * @param array     $usercontexts Contexts where the user has access. True if the user can access all contexts.
+      * @return SolrDisMaxQuery
+      */
+     protected function create_user_query($filters, $usercontexts) {
+         global $USER;
+         // Let's keep these changes internal.
+         $data = clone $filters;
+         $query = new \SolrDisMaxQuery();
+         $this->set_query($query, $data->q);
          $this->add_fields($query);
  
          // Search filters applied, we don't cache these filters as we don't want to pollute the cache with tmp filters
          // If the user can access all contexts $usercontexts value is just true, we don't need to filter
          // in that case.
          if ($usercontexts && is_array($usercontexts)) {
 -            if (!empty($data->areaid)) {
 -                $query->addFilterQuery('contextid:(' . implode(' OR ', $usercontexts[$data->areaid]) . ')');
 -            } else {
 -                // Join all area contexts into a single array and implode.
 -                $allcontexts = array();
 -                foreach ($usercontexts as $areacontexts) {
 -                    foreach ($areacontexts as $contextid) {
 -                        // Ensure they are unique.
 -                        $allcontexts[$contextid] = $contextid;
 -                    }
 +            // Join all area contexts into a single array and implode.
 +            $allcontexts = array();
 +            foreach ($usercontexts as $areaid => $areacontexts) {
 +                if (!empty($data->areaids) && !in_array($areaid, $data->areaids)) {
 +                    // Skip unused areas.
 +                    continue;
                  }
 -                $query->addFilterQuery('contextid:(' . implode(' OR ', $allcontexts) . ')');
 +                foreach ($areacontexts as $contextid) {
 +                    // Ensure they are unique.
 +                    $allcontexts[$contextid] = $contextid;
 +                }
 +            }
 +            if (empty($allcontexts)) {
 +                // This means there are no valid contexts for them, so they get no results.
 +                return array();
              }
 +            $query->addFilterQuery('contextid:(' . implode(' OR ', $allcontexts) . ')');
          }
  
-         try {
-             if ($this->file_indexing_enabled()) {
-                 // Now group records by solr_filegroupingid. Limit to 3 results per group.
-                 $query->setGroup(true);
-                 $query->setGroupLimit(3);
-                 $query->addGroupField('solr_filegroupingid');
-                 return $this->grouped_files_query_response($client->query($query));
-             } else {
-                 return $this->query_response($client->query($query));
-             }
-         } catch (\SolrClientException $ex) {
-             debugging('Error executing the provided query: ' . $ex->getMessage(), DEBUG_DEVELOPER);
-             $this->queryerror = $ex->getMessage();
-             return array();
-         } catch (\SolrServerException $ex) {
-             debugging('Error executing the provided query: ' . $ex->getMessage(), DEBUG_DEVELOPER);
-             $this->queryerror = $ex->getMessage();
-             return array();
+         if ($this->file_indexing_enabled()) {
+             // Now group records by solr_filegroupingid. Limit to 3 results per group.
+             $query->setGroup(true);
+             $query->setGroupLimit(3);
+             $query->setGroupNGroups(true);
+             $query->addGroupField('solr_filegroupingid');
          }
  
+         return $query;
      }
  
      /**
@@@ -188,39 -233,14 +233,39 @@@ class search_solr_engine_testcase exten
  
          // Title.
          unset($querydata->timeend);
-         $querydata->title = 'moodle/course:renameroles roleid 1';
+         $querydata->title = 'Special title';
          $this->assertCount(1, $this->search->search($querydata));
  
 +        // Course IDs.
 +        unset($querydata->title);
 +        $querydata->courseids = array(SITEID + 1);
 +        $this->assertCount(0, $this->search->search($querydata));
 +
 +        $querydata->courseids = array(SITEID);
 +        $this->assertCount(3, $this->search->search($querydata));
 +
 +        // Now try some area-id combinations.
 +        unset($querydata->courseids);
 +        $forumpostareaid = \core_search\manager::generate_areaid('mod_forum', 'post');
 +        $mockareaid = \core_search\manager::generate_areaid('core_mocksearch', 'role_capabilities');
 +
 +        $querydata->areaids = array($forumpostareaid);
 +        $this->assertCount(0, $this->search->search($querydata));
 +
 +        $querydata->areaids = array($forumpostareaid, $mockareaid);
 +        $this->assertCount(3, $this->search->search($querydata));
 +
 +        $querydata->areaids = array($mockareaid);
 +        $this->assertCount(3, $this->search->search($querydata));
 +
 +        $querydata->areaids = array();
 +        $this->assertCount(3, $this->search->search($querydata));
 +
          // Check that index contents get updated.
-         $DB->delete_records('role_capabilities', array('capability' => 'moodle/course:renameroles'));
+         $this->generator->delete_all();
          $this->search->index(true);
          unset($querydata->title);
-         $querydata->q = '*renameroles*';
+         $querydata->q = '*';
          $this->assertCount(0, $this->search->search($querydata));
      }
  
Simple merge