(bug 20275) Fixed LIKE queries on SQLite backend

* All manually built LIKE queries in the core are replaced with a wrapper function Database::buildLike()
* This function automatically performs all escaping, so Database::escapeLike() is now almost never used
This commit is contained in:
Max Semenik 2009-10-21 19:53:03 +00:00
parent f215649e2d
commit ae57ab1eec
33 changed files with 219 additions and 56 deletions

View file

@ -581,6 +581,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
dummy
* (bug 21161) Changing $wgCacheEpoch now always invalidates file cache
* (bug 20268) Fixed row count estimation on SQLite backend
* (bug 20275) Fixed LIKE queries on SQLite backend
== API changes in 1.16 ==

View file

@ -343,6 +343,7 @@ $wgAutoloadLocalClasses = array(
'LBFactory' => 'includes/db/LBFactory.php',
'LBFactory_Multi' => 'includes/db/LBFactory_Multi.php',
'LBFactory_Simple' => 'includes/db/LBFactory.php',
'LikeMatch' => 'includes/db/Database.php',
'LoadBalancer' => 'includes/db/LoadBalancer.php',
'LoadMonitor' => 'includes/db/LoadMonitor.php',
'LoadMonitor_MySQL' => 'includes/db/LoadMonitor.php',

View file

@ -286,7 +286,7 @@ class Block {
$options = array();
$db =& $this->getDBOptions( $options );
$conds = array(
"ipb_range_start LIKE '$range%'",
'ipb_range_start' . $db->buildLike( $range, $db->anyString() ),
"ipb_range_start <= '$iaddr'",
"ipb_range_end >= '$iaddr'"
);

View file

@ -49,8 +49,10 @@ class LinkFilter {
* @static
* @param $filterEntry String: domainparts
* @param $prot String: protocol
* @deprecated Use makeLikeArray() and pass result to Database::buildLike() instead
*/
public static function makeLike( $filterEntry , $prot = 'http://' ) {
wfDeprecated( __METHOD__ );
$db = wfGetDB( DB_MASTER );
if ( substr( $filterEntry, 0, 2 ) == '*.' ) {
$subdomains = true;
@ -105,4 +107,99 @@ class LinkFilter {
}
return $like;
}
/**
* Make an array to be used for calls to Database::like(), which will match the specified
* string. There are several kinds of filter entry:
* *.domain.com - Produces http://com.domain.%, matches domain.com
* and www.domain.com
* domain.com - Produces http://com.domain./%, matches domain.com
* or domain.com/ but not www.domain.com
* *.domain.com/x - Produces http://com.domain.%/x%, matches
* www.domain.com/xy
* domain.com/x - Produces http://com.domain./x%, matches
* domain.com/xy but not www.domain.com/xy
*
* Asterisks in any other location are considered invalid.
*
* @static
* @param $filterEntry String: domainparts
* @param $prot String: protocol
*/
public static function makeLikeArray( $filterEntry , $prot = 'http://' ) {
$db = wfGetDB( DB_MASTER );
if ( substr( $filterEntry, 0, 2 ) == '*.' ) {
$subdomains = true;
$filterEntry = substr( $filterEntry, 2 );
if ( $filterEntry == '' ) {
// We don't want to make a clause that will match everything,
// that could be dangerous
return false;
}
} else {
$subdomains = false;
}
// No stray asterisks, that could cause confusion
// It's not simple or efficient to handle it properly so we don't
// handle it at all.
if ( strpos( $filterEntry, '*' ) !== false ) {
return false;
}
$slash = strpos( $filterEntry, '/' );
if ( $slash !== false ) {
$path = substr( $filterEntry, $slash );
$host = substr( $filterEntry, 0, $slash );
} else {
$path = '/';
$host = $filterEntry;
}
// Reverse the labels in the hostname, convert to lower case
// For emails reverse domainpart only
if ( $prot == 'mailto:' && strpos($host, '@') ) {
// complete email adress
$mailparts = explode( '@', $host );
$domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
$host = $domainpart . '@' . $mailparts[0];
$like = array( "$prot$host", $db->anyString() );
} elseif ( $prot == 'mailto:' ) {
// domainpart of email adress only. do not add '.'
$host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
$like = array( "$prot$host", $db->anyString() );
} else {
$host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
if ( substr( $host, -1, 1 ) !== '.' ) {
$host .= '.';
}
$like = array( "$prot$host" );
if ( $subdomains ) {
$like[] = $db->anyString();
}
if ( !$subdomains || $path !== '/' ) {
$like[] = $path;
$like[] = $db->anyString();
}
}
return $like;
}
/**
* Filters an array returned by makeLikeArray(), removing everything past first pattern placeholder.
* @static
* @param $arr array: array to filter
* @return filtered array
*/
public static function keepOneWildcard( $arr ) {
if( !is_array( $arr ) ) {
return $arr;
}
foreach( $arr as $key => $value ) {
if ( $value instanceof LikeMatch ) {
return array_slice( $arr, 0, $key + 1 );
}
}
return $arr;
}
}

View file

@ -854,6 +854,8 @@ class LogPager extends ReverseChronologicalPager {
$this->title = $title->getPrefixedText();
$ns = $title->getNamespace();
$db = $this->mDb;
# Using the (log_namespace, log_title, log_timestamp) index with a
# range scan (LIKE) on the first two parts, instead of simple equality,
# makes it unusable for sorting. Sorted retrieval using another index
@ -866,10 +868,8 @@ class LogPager extends ReverseChronologicalPager {
# log entries for even the busiest pages, so it can be safely scanned
# in full to satisfy an impossible condition on user or similar.
if( $pattern && !$wgMiserMode ) {
# use escapeLike to avoid expensive search patterns like 't%st%'
$safetitle = $this->mDb->escapeLike( $title->getDBkey() );
$this->mConds['log_namespace'] = $ns;
$this->mConds[] = "log_title LIKE '$safetitle%'";
$this->mConds[] = 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() );
$this->pattern = $pattern;
} else {
$this->mConds['log_namespace'] = $ns;
@ -877,9 +877,9 @@ class LogPager extends ReverseChronologicalPager {
}
// Paranoia: avoid brute force searches (bug 17342)
if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
$this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::DELETED_ACTION) . ' = 0';
$this->mConds[] = $db->bitAnd('log_deleted', LogPage::DELETED_ACTION) . ' = 0';
} else if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
$this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::SUPPRESSED_ACTION) .
$this->mConds[] = $db->bitAnd('log_deleted', LogPage::SUPPRESSED_ACTION) .
' != ' . LogPage::SUPPRESSED_ACTION;
}
}

View file

@ -318,12 +318,11 @@ class MessageCache {
# database or in code.
if ( $code !== $wgContLanguageCode ) {
# Messages for particular language
$escapedCode = $dbr->escapeLike( $code );
$conds[] = "page_title like '%%/$escapedCode'";
$conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), "/$code" );
} else {
# Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
# other than language code.
$conds[] = "page_title not like '%%/%%'";
$conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
}
}

View file

@ -1663,8 +1663,7 @@ class Title {
$dbr = wfGetDB( DB_SLAVE );
$conds['page_namespace'] = $this->getNamespace();
$conds[] = 'page_title LIKE ' . $dbr->addQuotes(
$dbr->escapeLike( $this->getDBkey() ) . '/%' );
$conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
$options = array();
if( $limit > -1 )
$options['LIMIT'] = $limit;

View file

@ -60,7 +60,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
$from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from']));
$this->addWhereRange('cat_title', $dir, $from, null);
if (isset ($params['prefix']))
$this->addWhere("cat_title LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'");
$this->addWhere('cat_title' . $db->buildLike( $this->titlePartToKey($params['prefix']), $db->anyString() ) );
$this->addOption('LIMIT', $params['limit']+1);
$this->addOption('ORDER BY', 'cat_title' . ($params['dir'] == 'descending' ? ' DESC' : ''));

View file

@ -84,7 +84,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
if (!is_null($params['from']))
$this->addWhere('pl_title>=' . $db->addQuotes($this->titlePartToKey($params['from'])));
if (isset ($params['prefix']))
$this->addWhere("pl_title LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'");
$this->addWhere('pl_title' . $db->buildLike( $this->titlePartToKey($params['prefix']), $db->anyString() ) );
$this->addFields(array (
'pl_title',

View file

@ -61,7 +61,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
$this->addWhere('u1.user_name >= ' . $db->addQuotes($this->keyToTitle($params['from'])));
if (!is_null($params['prefix']))
$this->addWhere('u1.user_name LIKE "' . $db->escapeLike($this->keyToTitle( $params['prefix'])) . '%"');
$this->addWhere('u1.user_name' . $db->buildLike($this->keyToTitle($params['prefix']), $db->anyString()));
if (!is_null($params['group'])) {
// Filter only users that belong to a given group

View file

@ -76,7 +76,7 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
$from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from']));
$this->addWhereRange('img_name', $dir, $from, null);
if (isset ($params['prefix']))
$this->addWhere("img_name LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'");
$this->addWhere('img_name' . $db->buildLike( $this->titlePartToKey($params['prefix']), $db->anyString() ) );
if (isset ($params['minsize'])) {
$this->addWhere('img_size>=' . intval($params['minsize']));

View file

@ -65,7 +65,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
$from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from']));
$this->addWhereRange('page_title', $dir, $from, null);
if (isset ($params['prefix']))
$this->addWhere("page_title LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'");
$this->addWhere('page_title' . $db->buildLike($this->titlePartToKey($params['prefix']), $db->->anyString()));
if (is_null($resultPageSet)) {
$selectFields = array (

View file

@ -109,8 +109,10 @@ class ApiQueryBlocks extends ApiQueryBase {
else
$lower = $upper = IP::toHex($params['ip']);
$prefix = substr($lower, 0, 4);
$db = $this->getDB();
$this->addWhere(array(
"ipb_range_start LIKE '$prefix%'",
'ipb_range_start' . $db->buildLike($prefix, $db->anyString()),
"ipb_range_start <= '$lower'",
"ipb_range_end >= '$upper'"
));

View file

@ -77,14 +77,15 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
if(is_null($protocol))
$protocol = 'http://';
$likeQuery = LinkFilter::makeLike($query, $protocol);
$likeQuery = LinkFilter::makeLikeArray($query, $protocol);
if (!$likeQuery)
$this->dieUsage('Invalid query', 'bad_query');
$likeQuery = substr($likeQuery, 0, strpos($likeQuery,'%')+1);
$this->addWhere('el_index LIKE ' . $db->addQuotes( $likeQuery ));
$likeQuery = LinkFilter::keepOneWildcard($likeQuery);
$this->addWhere('el_index ' . $db->buildLike( $likeQuery ));
}
else if(!is_null($protocol))
$this->addWhere('el_index LIKE ' . $db->addQuotes( "$protocol%" ));
$this->addWhere('el_index ' . $db->buildLike( "$protocol", $db->anyString() ));
$prop = array_flip($params['prop']);
$fld_ids = isset($prop['ids']);

View file

@ -165,7 +165,7 @@ class ApiQueryContributions extends ApiQueryBase {
$this->addWhere($this->getDB()->bitAnd('rev_deleted',Revision::DELETED_USER) . ' = 0');
// We only want pages by the specified users.
if($this->prefixMode)
$this->addWhere("rev_user_text LIKE '" . $this->getDB()->escapeLike($this->userprefix) . "%'");
$this->addWhere('rev_user_text' . $this->getDB()->buildLike($this->userprefix, $this->getDB()->anyString()));
else
$this->addWhereFld('rev_user_text', $this->usernames);
// ... and in the specified timeframe.

View file

@ -1490,7 +1490,9 @@ abstract class DatabaseBase {
}
/**
* Escape string for safe LIKE usage
* Escape string for safe LIKE usage.
* WARNING: you should almost never use this function directly,
* instead use buildLike() that escapes everything automatically
*/
function escapeLike( $s ) {
$s = str_replace( '\\', '\\\\', $s );
@ -1499,6 +1501,48 @@ abstract class DatabaseBase {
return $s;
}
/**
* LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match
* containing either string literals that will be escaped or tokens returned by anyChar() or anyString().
* Alternatively, the function could be provided with an array of aforementioned parameters.
*
* Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns a LIKE clause that searches
* for subpages of 'My page title'.
* Alternatively: $pattern = array( 'My_page_title/', $dbr->anyString() ); $query .= $dbr->buildLike( $pattern );
*
* @ return String: fully built LIKE statement
*/
function buildLike() {
$params = func_get_args();
if (count($params) > 0 && is_array($params[0])) {
$params = $params[0];
}
$s = '';
foreach( $params as $value) {
if( $value instanceof LikeMatch ) {
$s .= $value->toString();
} else {
$s .= $this->escapeLike( $value );
}
}
return " LIKE '" . $s . "' ";
}
/**
* Returns a token for buildLike() that denotes a '_' to be used in a LIKE query
*/
function anyChar() {
return new LikeMatch( '_' );
}
/**
* Returns a token for buildLike() that denotes a '%' to be used in a LIKE query
*/
function anyString() {
return new LikeMatch( '%' );
}
/**
* Returns an appropriately quoted sequence value for inserting a new row.
* MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
@ -2779,3 +2823,19 @@ class ResultWrapper implements Iterator {
return $this->current() !== false;
}
}
/**
* Used by DatabaseBase::buildLike() to represent characters that have special meaning in SQL LIKE clauses
* and thus need no escaping. Don't instantiate it manually, use Database::anyChar() and anyString() instead.
*/
class LikeMatch {
private $str;
public function __construct( $s ) {
$this->str = $s;
}
public function toString() {
return $this->str;
}
}

View file

@ -418,6 +418,14 @@ class DatabaseSqlite extends DatabaseBase {
return $s;
}
function buildLike() {
$params = func_get_args();
if ( count( $params ) > 0 && is_array( $params[0] ) ) {
$params = $params[0];
}
return parent::buildLike( $params ) . "ESCAPE '\' ";
}
/**
* How lagged is this slave?
*/

View file

@ -49,7 +49,7 @@ class LocalRepo extends FSRepo {
$ext = File::normalizeExtension($ext);
$inuse = $dbw->selectField( 'oldimage', '1',
array( 'oi_sha1' => $sha1,
"oi_archive_name LIKE '%.{$ext}'",
'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ),
$dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE ),
__METHOD__, array( 'FOR UPDATE' ) );
}

View file

@ -262,10 +262,9 @@ class IPUnblockForm {
// Fixme -- encapsulate this sort of query-building.
$dbr = wfGetDB( DB_SLAVE );
$encIp = $dbr->addQuotes( IP::sanitizeIP($this->ip) );
$encRange = $dbr->addQuotes( "$range%" );
$encAddr = $dbr->addQuotes( $iaddr );
$conds[] = "(ipb_address = $encIp) OR
(ipb_range_start LIKE $encRange AND
(ipb_range_start" . $dbr->buildLike( $range, $dbr->anyString() ) . " AND
ipb_range_start <= $encAddr
AND ipb_range_end >= $encAddr)";
} else {

View file

@ -96,11 +96,11 @@ class LinkSearchPage extends QueryPage {
*/
static function mungeQuery( $query , $prot ) {
$field = 'el_index';
$rv = LinkFilter::makeLike( $query , $prot );
$rv = LinkFilter::makeLikeArray( $query , $prot );
if ($rv === false) {
//makeLike doesn't handle wildcard in IP, so we'll have to munge here.
if (preg_match('/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/', $query)) {
$rv = $prot . rtrim($query, " \t*") . '%';
$rv = array( $prot . rtrim($query, " \t*"), $dbr->anyString() );
$field = 'el_to';
}
}
@ -125,8 +125,8 @@ class LinkSearchPage extends QueryPage {
/* strip everything past first wildcard, so that index-based-only lookup would be done */
list( $munged, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt );
$stripped = substr($munged,0,strpos($munged,'%')+1);
$encSearch = $dbr->addQuotes( $stripped );
$stripped = LinkFilter::keepOneWildcard( $munged );
$like = $dbr->buildLike( $stripped );
$encSQL = '';
if ( isset ($this->mNs) && !$wgMiserMode )
@ -144,7 +144,7 @@ class LinkSearchPage extends QueryPage {
$externallinks $use_index
WHERE
page_id=el_from
AND $clause LIKE $encSearch
AND $clause $like
$encSQL";
}

View file

@ -37,10 +37,8 @@ class ImageListPager extends TablePager {
$nt = Title::newFromUrl( $search );
if( $nt ) {
$dbr = wfGetDB( DB_SLAVE );
$m = $dbr->strencode( strtolower( $nt->getDBkey() ) );
$m = str_replace( "%", "\\%", $m );
$m = str_replace( "_", "\\_", $m );
$this->mQueryConds = array( "LOWER(img_name) LIKE '%{$m}%'" );
$this->mQueryConds = array( 'LOWER(img_name)' . $dbr->buildLike( $dbr->anyString(),
strtolower( $nt->getDBkey() ), $dbr->anyString() ) );
}
}

View file

@ -416,7 +416,7 @@ class MovePageForm {
)
) ) {
$conds = array(
'page_title LIKE '.$dbr->addQuotes( $dbr->escapeLike( $ot->getDBkey() ) . '/%' )
'page_title' . $dbr->buildLike( $ot->getDBkey() . '/', $dbr->anyString() )
.' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() )
);
$conds['page_namespace'] = array();

View file

@ -69,8 +69,7 @@ function wfSpecialNewimages( $par, $specialPage ) {
if ( $wpIlMatch != '' && !$wgMiserMode) {
$nt = Title::newFromUrl( $wpIlMatch );
if( $nt ) {
$m = $dbr->escapeLike( strtolower( $nt->getDBkey() ) );
$where[] = "LOWER(img_name) LIKE '%{$m}%'";
$where[] = 'LOWER(img_name) ' . $dbr->buildLike( $dbr->anyString(), strtolower( $nt->getDBkey() ), $dbr->anyString() );
$searchpar['wpIlMatch'] = $wpIlMatch;
}
}

View file

@ -115,7 +115,7 @@ class SpecialPrefixindex extends SpecialAllpages {
array( 'page_namespace', 'page_title', 'page_is_redirect' ),
array(
'page_namespace' => $namespace,
'page_title LIKE \'' . $dbr->escapeLike( $prefixKey ) .'%\'',
'page_title' . $dbr->buildLike( $prefixKey, $dbr->anyString() ),
'page_title >= ' . $dbr->addQuotes( $fromKey ),
),
__METHOD__,

View file

@ -58,16 +58,15 @@ class PageArchive {
$title = Title::newFromText( $prefix );
if( $title ) {
$ns = $title->getNamespace();
$encPrefix = $dbr->escapeLike( $title->getDBkey() );
$prefix = $title->getDBkey();
} else {
// Prolly won't work too good
// @todo handle bare namespace names cleanly?
$ns = 0;
$encPrefix = $dbr->escapeLike( $prefix );
}
$conds = array(
'ar_namespace' => $ns,
"ar_title LIKE '$encPrefix%'",
'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
);
return self::listPages( $dbr, $conds );
}

View file

@ -53,7 +53,7 @@ class WithoutInterwikiPage extends PageQueryPage {
function getSQL() {
$dbr = wfGetDB( DB_SLAVE );
list( $page, $langlinks ) = $dbr->tableNamesN( 'page', 'langlinks' );
$prefix = $this->prefix ? "AND page_title LIKE '" . $dbr->escapeLike( $this->prefix ) . "%'" : '';
$prefix = $this->prefix ? 'AND page_title' . $dbr->buildLike( $this->prefix , $dbr->anyString() ) : '';
return
"SELECT 'Withoutinterwiki' AS type,
page_namespace AS namespace,

View file

@ -40,7 +40,7 @@ class CleanupSpam extends Maintenance {
$wgUser->addToDatabase();
}
$spec = $this->getArg();
$like = LinkFilter::makeLike( $spec );
$like = LinkFilter::makeLikeArray( $spec );
if ( !$like ) {
$this->error( "Not a valid hostname specification: $spec", true );
}
@ -53,7 +53,7 @@ class CleanupSpam extends Maintenance {
$dbr = wfGetDB( DB_SLAVE, array(), $wikiID );
$count = $dbr->selectField( 'externallinks', 'COUNT(*)',
array( 'el_index LIKE ' . $dbr->addQuotes( $like ) ), __METHOD__ );
array( 'el_index' . $dbr->buildLike( $like ) ), __METHOD__ );
if ( $count ) {
$found = true;
passthru( "php cleanupSpam.php --wiki='$wikiID' $spec | sed 's/^/$wikiID: /'" );
@ -69,7 +69,7 @@ class CleanupSpam extends Maintenance {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'externallinks', array( 'DISTINCT el_from' ),
array( 'el_index LIKE ' . $dbr->addQuotes( $like ) ), __METHOD__ );
array( 'el_index' . $dbr->buildLike( $like ) ), __METHOD__ );
$count = $dbr->numRows( $res );
$this->output( "Found $count articles containing $spec\n" );
foreach ( $res as $row ) {

View file

@ -7,8 +7,8 @@ $db = wfGetDB(DB_MASTER);
while (1) {
wfWaitForSlaves( 2 );
$db->commit();
$encServer = $db->escapeLike( $wgServer );
$q="DELETE /* deleteSelfExternals */ FROM externallinks WHERE el_to LIKE '$encServer/%' LIMIT 1000\n";
$q = $db->limitResult( "DELETE /* deleteSelfExternals */ FROM externallinks WHERE el_to"
. $db->buildLike( $wgServer . '/', $db->anyString() ), 1000 );
print "Deleting a batch\n";
$db->query($q);
if (!$db->affectedRows()) exit(0);

View file

@ -197,7 +197,6 @@ class NamespaceConflictChecker extends Maintenance {
$table = $this->db->tableName( $page );
$prefix = $this->db->strencode( $name );
$likeprefix = str_replace( '_', '\\_', $prefix);
$encNamespace = $this->db->addQuotes( $ns );
$titleSql = "TRIM(LEADING '$prefix:' FROM {$page}_title)";
@ -212,7 +211,7 @@ class NamespaceConflictChecker extends Maintenance {
$titleSql AS title
FROM {$table}
WHERE {$page}_namespace=0
AND {$page}_title LIKE '$likeprefix:%'";
AND {$page}_title " . $this->db->buildLike( $name . ':', $this-db->anyString() );
$result = $this->db->query( $sql, __METHOD__ );

View file

@ -105,7 +105,8 @@ function compressWithConcat( $startId, $maxChunkSize, $beginDate,
# overwriting bulk storage concat rows. Don't compress external references, because
# the script doesn't yet delete rows from external storage.
$conds = array(
"old_flags NOT LIKE '%object%' AND old_flags NOT LIKE '%external%'");
'old_flags NOT ' . $dbr->buildLike( MATCH_STRING, 'object', MATCH_STRING ) . ' AND old_flags NOT '
. $dbr->buildLike( MATCH_STRING, 'external', MATCH_STRING ) );
if ( $beginDate ) {
if ( !preg_match( '/^\d{14}$/', $beginDate ) ) {

View file

@ -62,7 +62,7 @@ function moveToExternal( $cluster, $maxID, $minID = 1 ) {
$res = $dbr->select( 'text', array( 'old_id', 'old_flags', 'old_text' ),
array(
"old_id BETWEEN $blockStart AND $blockEnd",
"old_flags NOT LIKE '%external%'",
'old_flags NOT ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ),
), $fname );
while ( $row = $dbr->fetchObject( $res ) ) {
# Resolve stubs

View file

@ -69,7 +69,7 @@ function resolveStub( $id, $stubText, $flags ) {
# Get the (maybe) external row
$externalRow = $dbr->selectRow( 'text', array( 'old_text' ),
array( 'old_id' => $stub->mOldId, "old_flags LIKE '%external%'" ),
array( 'old_id' => $stub->mOldId, 'old_flags' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ) ),
$fname
);

View file

@ -60,7 +60,7 @@ class TrackBlobs {
if ( $this->textClause != '' ) {
$this->textClause .= ' OR ';
}
$this->textClause .= 'old_text LIKE ' . $dbr->addQuotes( $dbr->escapeLike( "DB://$cluster/" ) . '%' );
$this->textClause .= 'old_text' . $dbr->buildLike( "DB://$cluster/", $dbr->anyString() );
}
}
return $this->textClause;
@ -99,7 +99,7 @@ class TrackBlobs {
'rev_id > ' . $dbr->addQuotes( $startId ),
'rev_text_id=old_id',
$textClause,
"old_flags LIKE '%external%'",
'old_flags ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ),
),
__METHOD__,
array(
@ -175,7 +175,7 @@ class TrackBlobs {
array(
'old_id>' . $dbr->addQuotes( $startId ),
$textClause,
"old_flags LIKE '%external%'",
'old_flags ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ),
'bt_text_id IS NULL'
),
__METHOD__,