EBernhardson has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/399312 )

Change subject: Push pagination decision for prefix search into SearchEngine
......................................................................

Push pagination decision for prefix search into SearchEngine

Various code using the search engine shouldn't need to implement it's
own methods, such as over-fetching, to determine if there are more
results available. This should be knowledge internal to search that is
exposed by a boolean.  Full-text search unfortunately does the same
thing, but fixing it is delegated to some future patch.

Change-Id: Ica094428700637dfdedb723b03f6aeadfe12b9f4
---
M includes/api/ApiQueryPrefixSearch.php
M includes/api/SearchApi.php
M includes/search/SearchEngine.php
M includes/search/SearchSuggestionSet.php
M tests/phpunit/includes/search/SearchEnginePrefixTest.php
5 files changed, 116 insertions(+), 47 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/12/399312/1

diff --git a/includes/api/ApiQueryPrefixSearch.php 
b/includes/api/ApiQueryPrefixSearch.php
index 2fbc518..aaf81b0 100644
--- a/includes/api/ApiQueryPrefixSearch.php
+++ b/includes/api/ApiQueryPrefixSearch.php
@@ -51,7 +51,12 @@
                $offset = $params['offset'];
 
                $searchEngine = $this->buildSearchEngine( $params );
-               $titles = $searchEngine->extractTitles( 
$searchEngine->completionSearchWithVariants( $search ) );
+               $suggestions = $searchEngine->completionSearchWithVariants( 
$search );
+               $titles = $searchEngine->extractTitles( $suggestions );
+
+               if ( $suggestions->hasMoreResults() ) {
+                       $this->setContinueEnumParameter( 'offset', $offset + 
$limit );
+               }
 
                if ( $resultPageSet ) {
                        $resultPageSet->setRedirectMergePolicy( function ( 
array $current, array $new ) {
@@ -60,10 +65,6 @@
                                }
                                return $current;
                        } );
-                       if ( count( $titles ) > $limit ) {
-                               $this->setContinueEnumParameter( 'offset', 
$offset + $limit );
-                               array_pop( $titles );
-                       }
                        $resultPageSet->populateFromTitles( $titles );
                        foreach ( $titles as $index => $title ) {
                                $resultPageSet->setGeneratorData( $title, [ 
'index' => $index + $offset + 1 ] );
@@ -72,10 +73,6 @@
                        $result = $this->getResult();
                        $count = 0;
                        foreach ( $titles as $title ) {
-                               if ( ++$count > $limit ) {
-                                       $this->setContinueEnumParameter( 
'offset', $offset + $limit );
-                                       break;
-                               }
                                $vals = [
                                        'ns' => intval( $title->getNamespace() 
),
                                        'title' => $title->getPrefixedText(),
diff --git a/includes/api/SearchApi.php b/includes/api/SearchApi.php
index f7c6471..fb6b635 100644
--- a/includes/api/SearchApi.php
+++ b/includes/api/SearchApi.php
@@ -157,15 +157,7 @@
                        $searchEngine = 
MediaWikiServices::getInstance()->getSearchEngineFactory()->create( $type );
                        $limit = $params['limit'];
                        $searchEngine->setNamespaces( $params['namespace'] );
-                       $offset = null;
-                       if ( isset( $params['offset'] ) ) {
-                               // If the API supports offset then it probably
-                               // wants to fetch limit+1 so it can check if
-                               // more results are available to properly set
-                               // the continue param
-                               $offset = $params['offset'];
-                               $limit += 1;
-                       }
+                       $offset = isset( $params['offset'] ) ? 
$params['offset'] : null;
                        $searchEngine->setLimitOffset( $limit, $offset );
 
                        // Initialize requested search profiles.
diff --git a/includes/search/SearchEngine.php b/includes/search/SearchEngine.php
index 3c8fe60..6876099 100644
--- a/includes/search/SearchEngine.php
+++ b/includes/search/SearchEngine.php
@@ -517,7 +517,15 @@
                        return SearchSuggestionSet::emptySuggestionSet(); // 
Return empty result
                }
                $search = $this->normalizeNamespaces( $search );
-               return $this->processCompletionResults( $search, 
$this->completionSearchBackend( $search ) );
+               // Over-fetch results so we can determine if pagination is 
possible in
+               // processCompletionResults
+               $this->limit++;
+               try {
+                       $suggestions = $this->completionSearchBackend( $search 
);
+               } finally {
+                       $this->limit--;
+               }
+               return $this->processCompletionResults( $search, $suggestions );
        }
 
        /**
@@ -531,23 +539,29 @@
                }
                $search = $this->normalizeNamespaces( $search );
 
-               $results = $this->completionSearchBackend( $search );
-               $fallbackLimit = $this->limit - $results->getSize();
-               if ( $fallbackLimit > 0 ) {
-                       global $wgContLang;
+               // Over-fetch results so we can determine if more results exist 
(for pagination).
+               $this->limit++;
+               try {
+                       $results = $this->completionSearchBackend( $search );
+                       $fallbackLimit = $this->limit - $results->getSize();
+                       if ( $fallbackLimit > 0 ) {
+                               global $wgContLang;
 
-                       $fallbackSearches = 
$wgContLang->autoConvertToAllVariants( $search );
-                       $fallbackSearches = array_diff( array_unique( 
$fallbackSearches ), [ $search ] );
+                               $fallbackSearches = 
$wgContLang->autoConvertToAllVariants( $search );
+                               $fallbackSearches = array_diff( array_unique( 
$fallbackSearches ), [ $search ] );
 
-                       foreach ( $fallbackSearches as $fbs ) {
-                               $this->setLimitOffset( $fallbackLimit );
-                               $fallbackSearchResult = 
$this->completionSearch( $fbs );
-                               $results->appendAll( $fallbackSearchResult );
-                               $fallbackLimit -= count( $fallbackSearchResult 
);
-                               if ( $fallbackLimit <= 0 ) {
-                                       break;
+                               foreach ( $fallbackSearches as $fbs ) {
+                                       $this->setLimitOffset( $fallbackLimit );
+                                       $fallbackSearchResult = 
$this->completionSearch( $fbs );
+                                       $results->appendAll( 
$fallbackSearchResult );
+                                       $fallbackLimit -= count( 
$fallbackSearchResult );
+                                       if ( $fallbackLimit <= 0 ) {
+                                               break;
+                                       }
                                }
                        }
+               } finally {
+                       $this->limit--;
                }
                return $this->processCompletionResults( $search, $results );
        }
@@ -571,6 +585,10 @@
         * @return SearchSuggestionSet
         */
        protected function processCompletionResults( $search, 
SearchSuggestionSet $suggestions ) {
+               // We over-fetched to determine pagination. Shrink back down if 
we have extra results
+               // and mark if pagination is possible
+               $suggestions->shrink( $this->limit );
+
                $search = trim( $search );
                // preload the titles with LinkBatch
                $titles = $suggestions->map( function ( SearchSuggestion $sugg 
) {
diff --git a/includes/search/SearchSuggestionSet.php 
b/includes/search/SearchSuggestionSet.php
index aced5e1..ab38420 100644
--- a/includes/search/SearchSuggestionSet.php
+++ b/includes/search/SearchSuggestionSet.php
@@ -36,6 +36,11 @@
        private $pageMap = [];
 
        /**
+        * @var bool Are more results available?
+        */
+       private $hasMoreResults;
+
+       /**
         * Builds a new set of suggestions.
         *
         * NOTE: the array should be sorted by score (higher is better),
@@ -45,8 +50,10 @@
         * unexpected behaviors.
         *
         * @param SearchSuggestion[] $suggestions (must be sorted by score)
+        * @param bool $hasMoreResults Are more results available?
         */
-       public function __construct( array $suggestions ) {
+       public function __construct( array $suggestions, $hasMoreResults = 
false ) {
+               $this->hasMoreResults = $hasMoreResults;
                foreach ( $suggestions as $suggestion ) {
                        $pageID = $suggestion->getSuggestedTitleID();
                        if ( $pageID && empty( $this->pageMap[$pageID] ) ) {
@@ -54,6 +61,13 @@
                        }
                        $this->suggestions[] = $suggestion;
                }
+       }
+
+       /**
+        * @return bool Are more results available?
+        */
+       public function hasMoreResults() {
+               return $this->hasMoreResults;
        }
 
        /**
@@ -167,6 +181,7 @@
        public function shrink( $limit ) {
                if ( count( $this->suggestions ) > $limit ) {
                        $this->suggestions = array_slice( $this->suggestions, 
0, $limit );
+                       $this->hasMoreResults = true;
                }
        }
 
@@ -177,14 +192,15 @@
         * NOTE: Suggestion scores will be generated.
         *
         * @param Title[] $titles
+        * @param bool $hasMoreResults Are more results available?
         * @return SearchSuggestionSet
         */
-       public static function fromTitles( array $titles ) {
+       public static function fromTitles( array $titles, $hasMoreResults = 
false ) {
                $score = count( $titles );
                $suggestions = array_map( function ( $title ) use ( &$score ) {
                        return SearchSuggestion::fromTitle( $score--, $title );
                }, $titles );
-               return new SearchSuggestionSet( $suggestions );
+               return new SearchSuggestionSet( $suggestions, $hasMoreResults );
        }
 
        /**
@@ -193,14 +209,15 @@
         * NOTE: Suggestion scores will be generated.
         *
         * @param string[] $titles
+        * @param bool $hasMoreResults Are more results available?
         * @return SearchSuggestionSet
         */
-       public static function fromStrings( array $titles ) {
+       public static function fromStrings( array $titles, $hasMoreResults = 
false ) {
                $score = count( $titles );
                $suggestions = array_map( function ( $title ) use ( &$score ) {
                        return SearchSuggestion::fromText( $score--, $title );
                }, $titles );
-               return new SearchSuggestionSet( $suggestions );
+               return new SearchSuggestionSet( $suggestions, $hasMoreResults );
        }
 
        /**
diff --git a/tests/phpunit/includes/search/SearchEnginePrefixTest.php 
b/tests/phpunit/includes/search/SearchEnginePrefixTest.php
index 4c5bab3..edc789c 100644
--- a/tests/phpunit/includes/search/SearchEnginePrefixTest.php
+++ b/tests/phpunit/includes/search/SearchEnginePrefixTest.php
@@ -329,6 +329,21 @@
                                        'Redirect test',
                                ],
                        ] ],
+                       [ [
+                               "Extra results must not be returned",
+                               'provision' => [
+                                       'Example',
+                                       'Example Bar',
+                                       'Example Foo',
+                                       'Example Foo/Bar'
+                               ],
+                               'query' => 'foo',
+                               'results' => [
+                                       'Example',
+                                       'Example Bar',
+                                       'Example Foo',
+                               ],
+                       ] ],
                ];
        }
 
@@ -337,16 +352,7 @@
         * @covers PrefixSearch::searchBackend
         */
        public function testSearchBackend( array $case ) {
-               $search = $stub = $this->getMockBuilder( 'SearchEngine' )
-                       ->setMethods( [ 'completionSearchBackend' ] 
)->getMock();
-
-               $return = SearchSuggestionSet::fromStrings( $case['provision'] 
);
-
-               $search->expects( $this->any() )
-                       ->method( 'completionSearchBackend' )
-                       ->will( $this->returnValue( $return ) );
-
-               $search->setLimitOffset( 3 );
+               $search = $this->mockSearchWithResults( $case['provision'] );
                $results = $search->completionSearch( $case['query'] );
 
                $results = $results->map( function ( SearchSuggestion $s ) {
@@ -359,4 +365,43 @@
                        $case[0]
                );
        }
+
+       public function paginationProvider() {
+               $res = [ 'Example', 'Example Bar', 'Example Foo', 'Example 
Foo/Bar' ];
+               return [
+                       'With less than requested results no pagination' => [
+                               false, array_slice( $res, 0, 2 ),
+                       ],
+                       'With same as requested results no pagination' => [
+                               false, array_slice( $res, 0, 3 ),
+                       ],
+                       'With extra result returned offer pagination' => [
+                               true, $res,
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider paginationProvider
+        */
+       public function testPagination( $hasMoreResults, $provision ) {
+               $search = $this->mockSearchWithResults( $provision );
+               $results = $search->completionSearch( 'irrelevant' );
+
+               $this->assertEquals( $hasMoreResults, 
$results->hasMoreResults() );
+       }
+
+       private function mockSearchWithResults( $titleStrings, $limit = 3 ) {
+               $search = $this->getMockBuilder( 'SearchEngine' )
+                       ->setMethods( [ 'completionSearchBackend' ] 
)->getMock();
+
+               $return = SearchSuggestionSet::fromStrings( $titleStrings );
+
+               $search->expects( $this->any() )
+                       ->method( 'completionSearchBackend' )
+                       ->will( $this->returnValue( $return ) );
+
+               $search->setLimitOffset( $limit );
+               return $search;
+       }
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/399312
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ica094428700637dfdedb723b03f6aeadfe12b9f4
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: EBernhardson <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to