(bug 20275) Fixed LIKE queries on SQLite backend
authorMax Semenik <maxsem@users.mediawiki.org>
Wed, 21 Oct 2009 19:53:03 +0000 (19:53 +0000)
committerMax Semenik <maxsem@users.mediawiki.org>
Wed, 21 Oct 2009 19:53:03 +0000 (19:53 +0000)
* 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

33 files changed:
RELEASE-NOTES
includes/AutoLoader.php
includes/Block.php
includes/LinkFilter.php
includes/LogEventsList.php
includes/MessageCache.php
includes/Title.php
includes/api/ApiQueryAllCategories.php
includes/api/ApiQueryAllLinks.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryAllimages.php
includes/api/ApiQueryAllpages.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryExtLinksUsage.php
includes/api/ApiQueryUserContributions.php
includes/db/Database.php
includes/db/DatabaseSqlite.php
includes/filerepo/LocalRepo.php
includes/specials/SpecialIpblocklist.php
includes/specials/SpecialLinkSearch.php
includes/specials/SpecialListfiles.php
includes/specials/SpecialMovepage.php
includes/specials/SpecialNewimages.php
includes/specials/SpecialPrefixindex.php
includes/specials/SpecialUndelete.php
includes/specials/SpecialWithoutinterwiki.php
maintenance/cleanupSpam.php
maintenance/deleteSelfExternals.php
maintenance/namespaceDupes.php
maintenance/storage/compressOld.inc
maintenance/storage/moveToExternal.php
maintenance/storage/resolveStubs.php
maintenance/storage/trackBlobs.php

index ed1a868..87f8e67 100644 (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 ==
 
index b24163d..5e2fce7 100644 (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',
index 7aa3065..0246908 100644 (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'"
                );
index dc4c125..af90a9b 100644 (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;
+       }
 }
index fce4e9c..fcd86e4 100644 (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;
                }
        }
index 2928117..e5eb705 100644 (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() );
                        }
                }
 
index 422a50b..44df731 100644 (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;
index b8a9380..b0927dc 100644 (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' : ''));
index 0f6e6bc..4df2373 100644 (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',
index fd36b19..21dcbb2 100644 (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
index 39a7ac7..e108a8f 100644 (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']));
index 4391290..558efd4 100644 (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 (
index f2f4a7f..62de51c 100644 (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'"
                        ));
index 8d76e8b..537a04d 100644 (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']);
index 9f739ec..756805c 100644 (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.
index e9e8753..e3d3906 100644 (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;
+       }
+}
\ No newline at end of file
index 720c3a1..138e9ca 100644 (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?
         */
index 3eca504..6c4d21a 100644 (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' ) );
                        }
index e017cec..c81ae49 100644 (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 {
index 5c343ed..8df21a6 100644 (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";
        }
 
index ae19ea9..7880097 100644 (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() ) );
                        }
                }
 
index 6ee29cc..ed5d03c 100644 (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();
index 96fea66..61f5230 100644 (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;
                }
        }
index 3a6b3c2..8b5f0c9 100644 (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__,
index fd9801b..0733d05 100644 (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 );
        }
index 740b434..a5d60d2 100644 (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,
index fb76e31..d1927f9 100644 (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 ) {
index e2e9789..7beda5d 100644 (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);
index b17011e..0e48a67 100644 (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__ );
 
index d79354f..60096ff 100644 (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 ) ) {
index a8b2f93..dc11856 100644 (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
index 3db9e48..0e51f0b 100644 (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
        );
 
index 8ac9474..5f25e39 100644 (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__,