From d16fb6515ba95bdc14221c3ba55bdfa1763a79a0 Mon Sep 17 00:00:00 2001 From: Aaron Schulz Date: Wed, 10 Apr 2019 21:45:53 -0700 Subject: [PATCH] search: refactor DatabaseSearch to take a load balancer instance Also make the update() methods of the subclasses use DB_MASTER as they should. This avoids read-only errors. In addition, avoid passing a dummy argument of null in some cases within SearchEngineFactory::create(). Fix some dynamic calls to static methods too. Change-Id: Id94f34994b0f9c18e23ef30cb2fe895e6dedd09c --- includes/search/SearchDatabase.php | 15 +++++--- includes/search/SearchEngineFactory.php | 35 ++++++++++------- includes/search/SearchMssql.php | 31 ++++++++++----- includes/search/SearchMySQL.php | 25 ++++++------ includes/search/SearchOracle.php | 29 +++++++++----- includes/search/SearchPostgres.php | 22 +++++++---- includes/search/SearchSqlite.php | 38 ++++++++++++------- .../includes/deferred/SearchUpdateTest.php | 3 -- .../includes/search/SearchEngineTest.php | 5 ++- 9 files changed, 125 insertions(+), 78 deletions(-) diff --git a/includes/search/SearchDatabase.php b/includes/search/SearchDatabase.php index 230cdedd71..6da8f98dc6 100644 --- a/includes/search/SearchDatabase.php +++ b/includes/search/SearchDatabase.php @@ -22,6 +22,7 @@ */ use Wikimedia\Rdbms\IDatabase; +use Wikimedia\Rdbms\ILoadBalancer; /** * Base search engine base class for database-backed searches @@ -29,16 +30,18 @@ use Wikimedia\Rdbms\IDatabase; * @since 1.23 */ abstract class SearchDatabase extends SearchEngine { - /** - * @var IDatabase Replica database from which to read results - */ + /** @var ILoadBalancer */ + protected $lb; + /** @var IDatabase (backwards compatibility) */ protected $db; /** - * @param IDatabase|null $db The database to search from + * @param ILoadBalancer $lb The load balancer for the DB cluster to search on */ - public function __construct( IDatabase $db = null ) { - $this->db = $db ?: wfGetDB( DB_REPLICA ); + public function __construct( ILoadBalancer $lb ) { + $this->lb = $lb; + // @TODO: remove this deprecated field in 1.35 + $this->db = $lb->getLazyConnectionRef( DB_REPLICA ); // b/c } /** diff --git a/includes/search/SearchEngineFactory.php b/includes/search/SearchEngineFactory.php index ecb6f43e64..6a69cd4ee9 100644 --- a/includes/search/SearchEngineFactory.php +++ b/includes/search/SearchEngineFactory.php @@ -1,6 +1,8 @@ config->getSearchType(); + $alternativesClasses = $this->config->getSearchTypes(); - $configType = $this->config->getSearchType(); - $alternatives = $this->config->getSearchTypes(); - - if ( $type && in_array( $type, $alternatives ) ) { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + if ( $type !== null && in_array( $type, $alternativesClasses ) ) { $class = $type; - } elseif ( $configType !== null ) { - $class = $configType; + } elseif ( $configuredClass !== null ) { + $class = $configuredClass; } else { - $dbr = wfGetDB( DB_REPLICA ); - $class = self::getSearchEngineClass( $dbr ); + $class = self::getSearchEngineClass( $lb ); } - $search = new $class( $dbr ); - return $search; + if ( is_subclass_of( $class, SearchDatabase::class ) ) { + return new $class( $lb ); + } else { + return new $class(); + } } /** - * @param IDatabase $db + * @param IDatabase|ILoadBalancer $dbOrLb * @return string SearchEngine subclass name * @since 1.28 */ - public static function getSearchEngineClass( IDatabase $db ) { - switch ( $db->getType() ) { + public static function getSearchEngineClass( $dbOrLb ) { + $type = ( $dbOrLb instanceof IDatabase ) + ? $dbOrLb->getType() + : $dbOrLb->getServerType( $dbOrLb->getWriterIndex() ); + + switch ( $type ) { case 'sqlite': return SearchSqlite::class; case 'mysql': diff --git a/includes/search/SearchMssql.php b/includes/search/SearchMssql.php index 0e85f9df2f..6a23bb344f 100644 --- a/includes/search/SearchMssql.php +++ b/includes/search/SearchMssql.php @@ -36,7 +36,9 @@ class SearchMssql extends SearchDatabase { * @return SqlSearchResultSet */ protected function doSearchTextInDB( $term ) { - $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), true ) ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $resultSet = $dbr->query( $this->getQuery( $this->filter( $term ), true ) ); + return new SqlSearchResultSet( $resultSet, $this->searchTerms ); } @@ -47,7 +49,9 @@ class SearchMssql extends SearchDatabase { * @return SqlSearchResultSet */ protected function doSearchTitleInDB( $term ) { - $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), false ) ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $resultSet = $dbr->query( $this->getQuery( $this->filter( $term ), false ) ); + return new SqlSearchResultSet( $resultSet, $this->searchTerms ); } @@ -72,7 +76,9 @@ class SearchMssql extends SearchDatabase { * @return string */ private function queryLimit( $sql ) { - return $this->db->limitResult( $sql, $this->limit, $this->offset ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + + return $dbr->limitResult( $sql, $this->limit, $this->offset ); } /** @@ -120,8 +126,9 @@ class SearchMssql extends SearchDatabase { */ private function queryMain( $filteredTerm, $fulltext ) { $match = $this->parseQuery( $filteredTerm, $fulltext ); - $page = $this->db->tableName( 'page' ); - $searchindex = $this->db->tableName( 'searchindex' ); + $dbr = $this->lb->getMaintenanceConnectionRef( DB_REPLICA ); + $page = $dbr->tableName( 'page' ); + $searchindex = $dbr->tableName( 'searchindex' ); return 'SELECT page_id, page_namespace, page_title, ftindex.[RANK]' . "FROM $page,FREETEXTTABLE($searchindex , $match, LANGUAGE 'English') as ftindex " . @@ -159,8 +166,10 @@ class SearchMssql extends SearchDatabase { } } - $searchon = $this->db->addQuotes( implode( ',', $q ) ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $searchon = $dbr->addQuotes( implode( ',', $q ) ); $field = $this->getIndexField( $fulltext ); + return "$field, $searchon"; } @@ -179,13 +188,14 @@ class SearchMssql extends SearchDatabase { // to properly decode the stream as UTF-8. SQL doesn't support UTF8 as a data type // but the indexer will correctly handle it by this method. Since all we are doing // is passing this data to the indexer and never retrieving it via PHP, this will save space - $table = $this->db->tableName( 'searchindex' ); + $dbr = $this->lb->getMaintenanceConnectionRef( DB_MASTER ); + $table = $dbr->tableName( 'searchindex' ); $utf8bom = '0xEFBBBF'; $si_title = $utf8bom . bin2hex( $title ); $si_text = $utf8bom . bin2hex( $text ); $sql = "DELETE FROM $table WHERE si_page = $id;"; $sql .= "INSERT INTO $table (si_page, si_title, si_text) VALUES ($id, $si_title, $si_text)"; - return $this->db->query( $sql, 'SearchMssql::update' ); + return $dbr->query( $sql, 'SearchMssql::update' ); } /** @@ -197,13 +207,14 @@ class SearchMssql extends SearchDatabase { * @return bool|IResultWrapper */ function updateTitle( $id, $title ) { - $table = $this->db->tableName( 'searchindex' ); + $dbr = $this->lb->getMaintenanceConnectionRef( DB_MASTER ); + $table = $dbr->tableName( 'searchindex' ); // see update for why we are using the utf8bom $utf8bom = '0xEFBBBF'; $si_title = $utf8bom . bin2hex( $title ); $sql = "DELETE FROM $table WHERE si_page = $id;"; $sql .= "INSERT INTO $table (si_page, si_title, si_text) VALUES ($id, $si_title, 0x00)"; - return $this->db->query( $sql, 'SearchMssql::updateTitle' ); + return $dbr->query( $sql, 'SearchMssql::updateTitle' ); } } diff --git a/includes/search/SearchMySQL.php b/includes/search/SearchMySQL.php index cae342670e..4a6b93b209 100644 --- a/includes/search/SearchMySQL.php +++ b/includes/search/SearchMySQL.php @@ -124,7 +124,8 @@ class SearchMySQL extends SearchDatabase { wfDebug( __METHOD__ . ": Can't understand search query '{$filteredText}'\n" ); } - $searchon = $this->db->addQuotes( $searchon ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $searchon = $dbr->addQuotes( $searchon ); $field = $this->getIndexField( $fulltext ); return [ " MATCH($field) AGAINST($searchon IN BOOLEAN MODE) ", @@ -186,14 +187,15 @@ class SearchMySQL extends SearchDatabase { $filteredTerm = $this->filter( $term ); $query = $this->getQuery( $filteredTerm, $fulltext ); - $resultSet = $this->db->select( + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $resultSet = $dbr->select( $query['tables'], $query['fields'], $query['conds'], __METHOD__, $query['options'], $query['joins'] ); $total = null; $query = $this->getCountQuery( $filteredTerm, $fulltext ); - $totalResult = $this->db->select( + $totalResult = $dbr->select( $query['tables'], $query['fields'], $query['conds'], __METHOD__, $query['options'], $query['joins'] ); @@ -224,7 +226,8 @@ class SearchMySQL extends SearchDatabase { protected function queryFeatures( &$query ) { foreach ( $this->features as $feature => $value ) { if ( $feature === 'title-suffix-filter' && $value ) { - $query['conds'][] = 'page_title' . $this->db->buildLike( $this->db->anyString(), $value ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $query['conds'][] = 'page_title' . $dbr->buildLike( $dbr->anyString(), $value ); } } } @@ -339,7 +342,7 @@ class SearchMySQL extends SearchDatabase { * @param string $text */ function update( $id, $title, $text ) { - $dbw = wfGetDB( DB_MASTER ); + $dbw = $this->lb->getConnectionRef( DB_MASTER ); $dbw->replace( 'searchindex', [ 'si_page' ], [ @@ -357,13 +360,12 @@ class SearchMySQL extends SearchDatabase { * @param string $title */ function updateTitle( $id, $title ) { - $dbw = wfGetDB( DB_MASTER ); - + $dbw = $this->lb->getConnectionRef( DB_MASTER ); $dbw->update( 'searchindex', [ 'si_title' => $this->normalizeText( $title ) ], [ 'si_page' => $id ], - __METHOD__, - [ $dbw->lowPriorityOption() ] ); + __METHOD__ + ); } /** @@ -374,8 +376,7 @@ class SearchMySQL extends SearchDatabase { * @param string $title Title of page that was deleted */ function delete( $id, $title ) { - $dbw = wfGetDB( DB_MASTER ); - + $dbw = $this->lb->getConnectionRef( DB_MASTER ); $dbw->delete( 'searchindex', [ 'si_page' => $id ], __METHOD__ ); } @@ -441,7 +442,7 @@ class SearchMySQL extends SearchDatabase { if ( is_null( self::$mMinSearchLength ) ) { $sql = "SHOW GLOBAL VARIABLES LIKE 'ft\\_min\\_word\\_len'"; - $dbr = wfGetDB( DB_REPLICA ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); $result = $dbr->query( $sql, __METHOD__ ); $row = $result->fetchObject(); $result->free(); diff --git a/includes/search/SearchOracle.php b/includes/search/SearchOracle.php index 6b2b4038dc..a5d351bcd7 100644 --- a/includes/search/SearchOracle.php +++ b/includes/search/SearchOracle.php @@ -71,7 +71,8 @@ class SearchOracle extends SearchDatabase { return new SqlSearchResultSet( false, '' ); } - $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), true ) ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $resultSet = $dbr->query( $this->getQuery( $this->filter( $term ), true ) ); return new SqlSearchResultSet( $resultSet, $this->searchTerms ); } @@ -86,7 +87,8 @@ class SearchOracle extends SearchDatabase { return new SqlSearchResultSet( false, '' ); } - $resultSet = $this->db->query( $this->getQuery( $this->filter( $term ), false ) ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $resultSet = $dbr->query( $this->getQuery( $this->filter( $term ), false ) ); return new SqlSearchResultSet( $resultSet, $this->searchTerms ); } @@ -101,7 +103,8 @@ class SearchOracle extends SearchDatabase { if ( $this->namespaces === [] ) { $namespaces = '0'; } else { - $namespaces = $this->db->makeList( $this->namespaces ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $namespaces = $dbr->makeList( $this->namespaces ); } return 'AND page_namespace IN (' . $namespaces . ')'; } @@ -114,7 +117,9 @@ class SearchOracle extends SearchDatabase { * @return string */ private function queryLimit( $sql ) { - return $this->db->limitResult( $sql, $this->limit, $this->offset ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + + return $dbr->limitResult( $sql, $this->limit, $this->offset ); } /** @@ -160,8 +165,11 @@ class SearchOracle extends SearchDatabase { */ function queryMain( $filteredTerm, $fulltext ) { $match = $this->parseQuery( $filteredTerm, $fulltext ); - $page = $this->db->tableName( 'page' ); - $searchindex = $this->db->tableName( 'searchindex' ); + + $dbr = $this->lb->getMaintenanceConnectionRef( DB_REPLICA ); + $page = $dbr->tableName( 'page' ); + $searchindex = $dbr->tableName( 'searchindex' ); + return 'SELECT page_id, page_namespace, page_title ' . "FROM $page,$searchindex " . 'WHERE page_id=si_page AND ' . $match; @@ -208,8 +216,10 @@ class SearchOracle extends SearchDatabase { } } - $searchon = $this->db->addQuotes( ltrim( $searchon, ' &' ) ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $searchon = $dbr->addQuotes( ltrim( $searchon, ' &' ) ); $field = $this->getIndexField( $fulltext ); + return " CONTAINS($field, $searchon, 1) > 0 "; } @@ -230,7 +240,7 @@ class SearchOracle extends SearchDatabase { * @param string $text */ function update( $id, $title, $text ) { - $dbw = wfGetDB( DB_MASTER ); + $dbw = $this->lb->getConnection( DB_MASTER ); $dbw->replace( 'searchindex', [ 'si_page' ], [ @@ -258,8 +268,7 @@ class SearchOracle extends SearchDatabase { * @param string $title */ function updateTitle( $id, $title ) { - $dbw = wfGetDB( DB_MASTER ); - + $dbw = $this->lb->getConnectionRef( DB_MASTER ); $dbw->update( 'searchindex', [ 'si_title' => $title ], [ 'si_page' => $id ], diff --git a/includes/search/SearchPostgres.php b/includes/search/SearchPostgres.php index 74ee552abb..63634cba7f 100644 --- a/includes/search/SearchPostgres.php +++ b/includes/search/SearchPostgres.php @@ -42,7 +42,8 @@ class SearchPostgres extends SearchDatabase { protected function doSearchTitleInDB( $term ) { $q = $this->searchQuery( $term, 'titlevector', 'page_title' ); $olderror = error_reporting( E_ERROR ); - $resultSet = $this->db->query( $q, 'SearchPostgres', true ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $resultSet = $dbr->query( $q, 'SearchPostgres', true ); error_reporting( $olderror ); return new SqlSearchResultSet( $resultSet, $this->searchTerms ); } @@ -50,7 +51,8 @@ class SearchPostgres extends SearchDatabase { protected function doSearchTextInDB( $term ) { $q = $this->searchQuery( $term, 'textvector', 'old_text' ); $olderror = error_reporting( E_ERROR ); - $resultSet = $this->db->query( $q, 'SearchPostgres', true ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $resultSet = $dbr->query( $q, 'SearchPostgres', true ); error_reporting( $olderror ); return new SqlSearchResultSet( $resultSet, $this->searchTerms ); } @@ -111,7 +113,8 @@ class SearchPostgres extends SearchDatabase { $searchstring = preg_replace( '/^[\'"](.*)[\'"]$/', "$1", $searchstring ); # # Quote the whole thing - $searchstring = $this->db->addQuotes( $searchstring ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $searchstring = $dbr->addQuotes( $searchstring ); wfDebug( "parseQuery returned: $searchstring \n" ); @@ -131,7 +134,8 @@ class SearchPostgres extends SearchDatabase { # # We need a separate query here so gin does not complain about empty searches $sql = "SELECT to_tsquery($searchstring)"; - $res = $this->db->query( $sql ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $res = $dbr->query( $sql ); if ( !$res ) { # # TODO: Better output (example to catch: one 'two) die( "Sorry, that was not a valid search string. Please go back and try again" ); @@ -172,14 +176,14 @@ class SearchPostgres extends SearchDatabase { if ( count( $this->namespaces ) < 1 ) { $query .= ' AND page_namespace = 0'; } else { - $namespaces = $this->db->makeList( $this->namespaces ); + $namespaces = $dbr->makeList( $this->namespaces ); $query .= " AND page_namespace IN ($namespaces)"; } } $query .= " ORDER BY score DESC, page_id DESC"; - $query .= $this->db->limitResult( '', $this->limit, $this->offset ); + $query .= $dbr->limitResult( '', $this->limit, $this->offset ); wfDebug( "searchQuery returned: $query \n" ); @@ -201,12 +205,14 @@ class SearchPostgres extends SearchDatabase { " AND s.slot_role_id = " . $slotRoleStore->getId( SlotRecord::MAIN ) . " " . " AND c.content_id = s.slot_content_id " . " ORDER BY old_rev_text_id DESC OFFSET 1)"; - $this->db->query( $sql ); + + $dbw = $this->lb->getConnectionRef( DB_MASTER ); + $dbw->query( $sql ); + return true; } function updateTitle( $id, $title ) { return true; } - } diff --git a/includes/search/SearchSqlite.php b/includes/search/SearchSqlite.php index c30479766e..3646b274ed 100644 --- a/includes/search/SearchSqlite.php +++ b/includes/search/SearchSqlite.php @@ -22,6 +22,7 @@ */ use MediaWiki\MediaWikiServices; +use Wikimedia\Rdbms\DatabaseSqlite; /** * Search engine hook for SQLite @@ -33,7 +34,10 @@ class SearchSqlite extends SearchDatabase { * @return bool */ function fulltextSearchSupported() { - return $this->db->checkForEnabledSearch(); + /** @var DatabaseSqlite $dbr */ + $dbr = $this->lb->getConnection( DB_REPLICA ); + + return $dbr->checkForEnabledSearch(); } /** @@ -120,8 +124,10 @@ class SearchSqlite extends SearchDatabase { wfDebug( __METHOD__ . ": Can't understand search query '{$filteredText}'\n" ); } - $searchon = $this->db->addQuotes( $searchon ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $searchon = $dbr->addQuotes( $searchon ); $field = $this->getIndexField( $fulltext ); + return " $field MATCH $searchon "; } @@ -178,10 +184,11 @@ class SearchSqlite extends SearchDatabase { $filteredTerm = $this->filter( MediaWikiServices::getInstance()->getContentLanguage()->lc( $term ) ); - $resultSet = $this->db->query( $this->getQuery( $filteredTerm, $fulltext ) ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $resultSet = $dbr->query( $this->getQuery( $filteredTerm, $fulltext ) ); $total = null; - $totalResult = $this->db->query( $this->getCountQuery( $filteredTerm, $fulltext ) ); + $totalResult = $dbr->query( $this->getCountQuery( $filteredTerm, $fulltext ) ); $row = $totalResult->fetchObject(); if ( $row ) { $total = intval( $row->c ); @@ -202,7 +209,8 @@ class SearchSqlite extends SearchDatabase { if ( $this->namespaces === [] ) { $namespaces = '0'; } else { - $namespaces = $this->db->makeList( $this->namespaces ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + $namespaces = $dbr->makeList( $this->namespaces ); } return 'AND page_namespace IN (' . $namespaces . ')'; } @@ -213,7 +221,9 @@ class SearchSqlite extends SearchDatabase { * @return string */ private function limitResult( $sql ) { - return $this->db->limitResult( $sql, $this->limit, $this->offset ); + $dbr = $this->lb->getConnectionRef( DB_REPLICA ); + + return $dbr->limitResult( $sql, $this->limit, $this->offset ); } /** @@ -248,8 +258,9 @@ class SearchSqlite extends SearchDatabase { */ private function queryMain( $filteredTerm, $fulltext ) { $match = $this->parseQuery( $filteredTerm, $fulltext ); - $page = $this->db->tableName( 'page' ); - $searchindex = $this->db->tableName( 'searchindex' ); + $dbr = $this->lb->getMaintenanceConnectionRef( DB_REPLICA ); + $page = $dbr->tableName( 'page' ); + $searchindex = $dbr->tableName( 'searchindex' ); return "SELECT $searchindex.rowid, page_namespace, page_title " . "FROM $page,$searchindex " . "WHERE page_id=$searchindex.rowid AND $match"; @@ -257,8 +268,9 @@ class SearchSqlite extends SearchDatabase { private function getCountQuery( $filteredTerm, $fulltext ) { $match = $this->parseQuery( $filteredTerm, $fulltext ); - $page = $this->db->tableName( 'page' ); - $searchindex = $this->db->tableName( 'searchindex' ); + $dbr = $this->lb->getMaintenanceConnectionRef( DB_REPLICA ); + $page = $dbr->tableName( 'page' ); + $searchindex = $dbr->tableName( 'searchindex' ); return "SELECT COUNT(*) AS c " . "FROM $page,$searchindex " . "WHERE page_id=$searchindex.rowid AND $match " . @@ -279,10 +291,8 @@ class SearchSqlite extends SearchDatabase { } // @todo find a method to do it in a single request, // couldn't do it so far due to typelessness of FTS3 tables. - $dbw = wfGetDB( DB_MASTER ); - + $dbw = $this->lb->getConnectionRef( DB_MASTER ); $dbw->delete( 'searchindex', [ 'rowid' => $id ], __METHOD__ ); - $dbw->insert( 'searchindex', [ 'rowid' => $id, @@ -302,8 +312,8 @@ class SearchSqlite extends SearchDatabase { if ( !$this->fulltextSearchSupported() ) { return; } - $dbw = wfGetDB( DB_MASTER ); + $dbw = $this->lb->getConnectionRef( DB_MASTER ); $dbw->update( 'searchindex', [ 'si_title' => $title ], [ 'rowid' => $id ], diff --git a/tests/phpunit/includes/deferred/SearchUpdateTest.php b/tests/phpunit/includes/deferred/SearchUpdateTest.php index 74a5e3c470..8faaeda0d2 100644 --- a/tests/phpunit/includes/deferred/SearchUpdateTest.php +++ b/tests/phpunit/includes/deferred/SearchUpdateTest.php @@ -76,9 +76,6 @@ class MockSearch extends SearchEngine { public static $title; public static $text; - public function __construct( $db ) { - } - public function update( $id, $title, $text ) { self::$id = $id; self::$title = $title; diff --git a/tests/phpunit/includes/search/SearchEngineTest.php b/tests/phpunit/includes/search/SearchEngineTest.php index 0c6520eed5..1a0393e546 100644 --- a/tests/phpunit/includes/search/SearchEngineTest.php +++ b/tests/phpunit/includes/search/SearchEngineTest.php @@ -1,5 +1,7 @@ search = new $searchType( $this->db ); + $lb = LoadBalancerSingle::newFromConnection( $this->db ); + $this->search = new $searchType( $lb ); } protected function tearDown() { -- 2.20.1