r57989 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r57988‎ | r57989 | r57990 >
Date:19:53, 21 October 2009
Author:maxsem
Status:resolved (Comments)
Tags:
Comment:
(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
Modified paths:
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/Block.php (modified) (history)
  • /trunk/phase3/includes/LinkFilter.php (modified) (history)
  • /trunk/phase3/includes/LogEventsList.php (modified) (history)
  • /trunk/phase3/includes/MessageCache.php (modified) (history)
  • /trunk/phase3/includes/Title.php (modified) (history)
  • /trunk/phase3/includes/api/ApiQueryAllCategories.php (modified) (history)
  • /trunk/phase3/includes/api/ApiQueryAllLinks.php (modified) (history)
  • /trunk/phase3/includes/api/ApiQueryAllUsers.php (modified) (history)
  • /trunk/phase3/includes/api/ApiQueryAllimages.php (modified) (history)
  • /trunk/phase3/includes/api/ApiQueryAllpages.php (modified) (history)
  • /trunk/phase3/includes/api/ApiQueryBlocks.php (modified) (history)
  • /trunk/phase3/includes/api/ApiQueryExtLinksUsage.php (modified) (history)
  • /trunk/phase3/includes/api/ApiQueryUserContributions.php (modified) (history)
  • /trunk/phase3/includes/db/Database.php (modified) (history)
  • /trunk/phase3/includes/db/DatabaseSqlite.php (modified) (history)
  • /trunk/phase3/includes/filerepo/LocalRepo.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialIpblocklist.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialLinkSearch.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialListfiles.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialMovepage.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialNewimages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialPrefixindex.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialUndelete.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialWithoutinterwiki.php (modified) (history)
  • /trunk/phase3/maintenance/cleanupSpam.php (modified) (history)
  • /trunk/phase3/maintenance/deleteSelfExternals.php (modified) (history)
  • /trunk/phase3/maintenance/namespaceDupes.php (modified) (history)
  • /trunk/phase3/maintenance/storage/compressOld.inc (modified) (history)
  • /trunk/phase3/maintenance/storage/moveToExternal.php (modified) (history)
  • /trunk/phase3/maintenance/storage/resolveStubs.php (modified) (history)
  • /trunk/phase3/maintenance/storage/trackBlobs.php (modified) (history)

Diff [purge]

Index: trunk/phase3/RELEASE-NOTES
@@ -581,6 +581,7 @@
582582 dummy
583583 * (bug 21161) Changing $wgCacheEpoch now always invalidates file cache
584584 * (bug 20268) Fixed row count estimation on SQLite backend
 585+* (bug 20275) Fixed LIKE queries on SQLite backend
585586
586587 == API changes in 1.16 ==
587588
Index: trunk/phase3/maintenance/cleanupSpam.php
@@ -40,7 +40,7 @@
4141 $wgUser->addToDatabase();
4242 }
4343 $spec = $this->getArg();
44 - $like = LinkFilter::makeLike( $spec );
 44+ $like = LinkFilter::makeLikeArray( $spec );
4545 if ( !$like ) {
4646 $this->error( "Not a valid hostname specification: $spec", true );
4747 }
@@ -53,7 +53,7 @@
5454 $dbr = wfGetDB( DB_SLAVE, array(), $wikiID );
5555
5656 $count = $dbr->selectField( 'externallinks', 'COUNT(*)',
57 - array( 'el_index LIKE ' . $dbr->addQuotes( $like ) ), __METHOD__ );
 57+ array( 'el_index' . $dbr->buildLike( $like ) ), __METHOD__ );
5858 if ( $count ) {
5959 $found = true;
6060 passthru( "php cleanupSpam.php --wiki='$wikiID' $spec | sed 's/^/$wikiID: /'" );
@@ -69,7 +69,7 @@
7070
7171 $dbr = wfGetDB( DB_SLAVE );
7272 $res = $dbr->select( 'externallinks', array( 'DISTINCT el_from' ),
73 - array( 'el_index LIKE ' . $dbr->addQuotes( $like ) ), __METHOD__ );
 73+ array( 'el_index' . $dbr->buildLike( $like ) ), __METHOD__ );
7474 $count = $dbr->numRows( $res );
7575 $this->output( "Found $count articles containing $spec\n" );
7676 foreach ( $res as $row ) {
Index: trunk/phase3/maintenance/storage/resolveStubs.php
@@ -69,7 +69,7 @@
7070
7171 # Get the (maybe) external row
7272 $externalRow = $dbr->selectRow( 'text', array( 'old_text' ),
73 - array( 'old_id' => $stub->mOldId, "old_flags LIKE '%external%'" ),
 73+ array( 'old_id' => $stub->mOldId, 'old_flags' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ) ),
7474 $fname
7575 );
7676
Index: trunk/phase3/maintenance/storage/trackBlobs.php
@@ -60,7 +60,7 @@
6161 if ( $this->textClause != '' ) {
6262 $this->textClause .= ' OR ';
6363 }
64 - $this->textClause .= 'old_text LIKE ' . $dbr->addQuotes( $dbr->escapeLike( "DB://$cluster/" ) . '%' );
 64+ $this->textClause .= 'old_text' . $dbr->buildLike( "DB://$cluster/", $dbr->anyString() );
6565 }
6666 }
6767 return $this->textClause;
@@ -99,7 +99,7 @@
100100 'rev_id > ' . $dbr->addQuotes( $startId ),
101101 'rev_text_id=old_id',
102102 $textClause,
103 - "old_flags LIKE '%external%'",
 103+ 'old_flags ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ),
104104 ),
105105 __METHOD__,
106106 array(
@@ -175,7 +175,7 @@
176176 array(
177177 'old_id>' . $dbr->addQuotes( $startId ),
178178 $textClause,
179 - "old_flags LIKE '%external%'",
 179+ 'old_flags ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ),
180180 'bt_text_id IS NULL'
181181 ),
182182 __METHOD__,
Index: trunk/phase3/maintenance/storage/compressOld.inc
@@ -105,7 +105,8 @@
106106 # overwriting bulk storage concat rows. Don't compress external references, because
107107 # the script doesn't yet delete rows from external storage.
108108 $conds = array(
109 - "old_flags NOT LIKE '%object%' AND old_flags NOT LIKE '%external%'");
 109+ 'old_flags NOT ' . $dbr->buildLike( MATCH_STRING, 'object', MATCH_STRING ) . ' AND old_flags NOT '
 110+ . $dbr->buildLike( MATCH_STRING, 'external', MATCH_STRING ) );
110111
111112 if ( $beginDate ) {
112113 if ( !preg_match( '/^\d{14}$/', $beginDate ) ) {
Index: trunk/phase3/maintenance/storage/moveToExternal.php
@@ -62,7 +62,7 @@
6363 $res = $dbr->select( 'text', array( 'old_id', 'old_flags', 'old_text' ),
6464 array(
6565 "old_id BETWEEN $blockStart AND $blockEnd",
66 - "old_flags NOT LIKE '%external%'",
 66+ 'old_flags NOT ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ),
6767 ), $fname );
6868 while ( $row = $dbr->fetchObject( $res ) ) {
6969 # Resolve stubs
Index: trunk/phase3/maintenance/namespaceDupes.php
@@ -197,7 +197,6 @@
198198 $table = $this->db->tableName( $page );
199199
200200 $prefix = $this->db->strencode( $name );
201 - $likeprefix = str_replace( '_', '\\_', $prefix);
202201 $encNamespace = $this->db->addQuotes( $ns );
203202
204203 $titleSql = "TRIM(LEADING '$prefix:' FROM {$page}_title)";
@@ -212,7 +211,7 @@
213212 $titleSql AS title
214213 FROM {$table}
215214 WHERE {$page}_namespace=0
216 - AND {$page}_title LIKE '$likeprefix:%'";
 215+ AND {$page}_title " . $this->db->buildLike( $name . ':', $this-db->anyString() );
217216
218217 $result = $this->db->query( $sql, __METHOD__ );
219218
Index: trunk/phase3/maintenance/deleteSelfExternals.php
@@ -7,8 +7,8 @@
88 while (1) {
99 wfWaitForSlaves( 2 );
1010 $db->commit();
11 - $encServer = $db->escapeLike( $wgServer );
12 - $q="DELETE /* deleteSelfExternals */ FROM externallinks WHERE el_to LIKE '$encServer/%' LIMIT 1000\n";
 11+ $q = $db->limitResult( "DELETE /* deleteSelfExternals */ FROM externallinks WHERE el_to"
 12+ . $db->buildLike( $wgServer . '/', $db->anyString() ), 1000 );
1313 print "Deleting a batch\n";
1414 $db->query($q);
1515 if (!$db->affectedRows()) exit(0);
Index: trunk/phase3/includes/Title.php
@@ -1663,8 +1663,7 @@
16641664
16651665 $dbr = wfGetDB( DB_SLAVE );
16661666 $conds['page_namespace'] = $this->getNamespace();
1667 - $conds[] = 'page_title LIKE ' . $dbr->addQuotes(
1668 - $dbr->escapeLike( $this->getDBkey() ) . '/%' );
 1667+ $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
16691668 $options = array();
16701669 if( $limit > -1 )
16711670 $options['LIMIT'] = $limit;
Index: trunk/phase3/includes/Block.php
@@ -286,7 +286,7 @@
287287 $options = array();
288288 $db =& $this->getDBOptions( $options );
289289 $conds = array(
290 - "ipb_range_start LIKE '$range%'",
 290+ 'ipb_range_start' . $db->buildLike( $range, $db->anyString() ),
291291 "ipb_range_start <= '$iaddr'",
292292 "ipb_range_end >= '$iaddr'"
293293 );
Index: trunk/phase3/includes/specials/SpecialLinkSearch.php
@@ -96,11 +96,11 @@
9797 */
9898 static function mungeQuery( $query , $prot ) {
9999 $field = 'el_index';
100 - $rv = LinkFilter::makeLike( $query , $prot );
 100+ $rv = LinkFilter::makeLikeArray( $query , $prot );
101101 if ($rv === false) {
102102 //makeLike doesn't handle wildcard in IP, so we'll have to munge here.
103103 if (preg_match('/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/', $query)) {
104 - $rv = $prot . rtrim($query, " \t*") . '%';
 104+ $rv = array( $prot . rtrim($query, " \t*"), $dbr->anyString() );
105105 $field = 'el_to';
106106 }
107107 }
@@ -125,8 +125,8 @@
126126
127127 /* strip everything past first wildcard, so that index-based-only lookup would be done */
128128 list( $munged, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt );
129 - $stripped = substr($munged,0,strpos($munged,'%')+1);
130 - $encSearch = $dbr->addQuotes( $stripped );
 129+ $stripped = LinkFilter::keepOneWildcard( $munged );
 130+ $like = $dbr->buildLike( $stripped );
131131
132132 $encSQL = '';
133133 if ( isset ($this->mNs) && !$wgMiserMode )
@@ -144,7 +144,7 @@
145145 $externallinks $use_index
146146 WHERE
147147 page_id=el_from
148 - AND $clause LIKE $encSearch
 148+ AND $clause $like
149149 $encSQL";
150150 }
151151
Index: trunk/phase3/includes/specials/SpecialNewimages.php
@@ -69,8 +69,7 @@
7070 if ( $wpIlMatch != '' && !$wgMiserMode) {
7171 $nt = Title::newFromUrl( $wpIlMatch );
7272 if( $nt ) {
73 - $m = $dbr->escapeLike( strtolower( $nt->getDBkey() ) );
74 - $where[] = "LOWER(img_name) LIKE '%{$m}%'";
 73+ $where[] = 'LOWER(img_name) ' . $dbr->buildLike( $dbr->anyString(), strtolower( $nt->getDBkey() ), $dbr->anyString() );
7574 $searchpar['wpIlMatch'] = $wpIlMatch;
7675 }
7776 }
Index: trunk/phase3/includes/specials/SpecialPrefixindex.php
@@ -115,7 +115,7 @@
116116 array( 'page_namespace', 'page_title', 'page_is_redirect' ),
117117 array(
118118 'page_namespace' => $namespace,
119 - 'page_title LIKE \'' . $dbr->escapeLike( $prefixKey ) .'%\'',
 119+ 'page_title' . $dbr->buildLike( $prefixKey, $dbr->anyString() ),
120120 'page_title >= ' . $dbr->addQuotes( $fromKey ),
121121 ),
122122 __METHOD__,
Index: trunk/phase3/includes/specials/SpecialWithoutinterwiki.php
@@ -53,7 +53,7 @@
5454 function getSQL() {
5555 $dbr = wfGetDB( DB_SLAVE );
5656 list( $page, $langlinks ) = $dbr->tableNamesN( 'page', 'langlinks' );
57 - $prefix = $this->prefix ? "AND page_title LIKE '" . $dbr->escapeLike( $this->prefix ) . "%'" : '';
 57+ $prefix = $this->prefix ? 'AND page_title' . $dbr->buildLike( $this->prefix , $dbr->anyString() ) : '';
5858 return
5959 "SELECT 'Withoutinterwiki' AS type,
6060 page_namespace AS namespace,
Index: trunk/phase3/includes/specials/SpecialMovepage.php
@@ -416,7 +416,7 @@
417417 )
418418 ) ) {
419419 $conds = array(
420 - 'page_title LIKE '.$dbr->addQuotes( $dbr->escapeLike( $ot->getDBkey() ) . '/%' )
 420+ 'page_title' . $dbr->buildLike( $ot->getDBkey() . '/', $dbr->anyString() )
421421 .' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() )
422422 );
423423 $conds['page_namespace'] = array();
Index: trunk/phase3/includes/specials/SpecialListfiles.php
@@ -37,10 +37,8 @@
3838 $nt = Title::newFromUrl( $search );
3939 if( $nt ) {
4040 $dbr = wfGetDB( DB_SLAVE );
41 - $m = $dbr->strencode( strtolower( $nt->getDBkey() ) );
42 - $m = str_replace( "%", "\\%", $m );
43 - $m = str_replace( "_", "\\_", $m );
44 - $this->mQueryConds = array( "LOWER(img_name) LIKE '%{$m}%'" );
 41+ $this->mQueryConds = array( 'LOWER(img_name)' . $dbr->buildLike( $dbr->anyString(),
 42+ strtolower( $nt->getDBkey() ), $dbr->anyString() ) );
4543 }
4644 }
4745
Index: trunk/phase3/includes/specials/SpecialUndelete.php
@@ -58,16 +58,15 @@
5959 $title = Title::newFromText( $prefix );
6060 if( $title ) {
6161 $ns = $title->getNamespace();
62 - $encPrefix = $dbr->escapeLike( $title->getDBkey() );
 62+ $prefix = $title->getDBkey();
6363 } else {
6464 // Prolly won't work too good
6565 // @todo handle bare namespace names cleanly?
6666 $ns = 0;
67 - $encPrefix = $dbr->escapeLike( $prefix );
6867 }
6968 $conds = array(
7069 'ar_namespace' => $ns,
71 - "ar_title LIKE '$encPrefix%'",
 70+ 'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
7271 );
7372 return self::listPages( $dbr, $conds );
7473 }
Index: trunk/phase3/includes/specials/SpecialIpblocklist.php
@@ -262,10 +262,9 @@
263263 // Fixme -- encapsulate this sort of query-building.
264264 $dbr = wfGetDB( DB_SLAVE );
265265 $encIp = $dbr->addQuotes( IP::sanitizeIP($this->ip) );
266 - $encRange = $dbr->addQuotes( "$range%" );
267266 $encAddr = $dbr->addQuotes( $iaddr );
268267 $conds[] = "(ipb_address = $encIp) OR
269 - (ipb_range_start LIKE $encRange AND
 268+ (ipb_range_start" . $dbr->buildLike( $range, $dbr->anyString() ) . " AND
270269 ipb_range_start <= $encAddr
271270 AND ipb_range_end >= $encAddr)";
272271 } else {
Index: trunk/phase3/includes/AutoLoader.php
@@ -343,6 +343,7 @@
344344 'LBFactory' => 'includes/db/LBFactory.php',
345345 'LBFactory_Multi' => 'includes/db/LBFactory_Multi.php',
346346 'LBFactory_Simple' => 'includes/db/LBFactory.php',
 347+ 'LikeMatch' => 'includes/db/Database.php',
347348 'LoadBalancer' => 'includes/db/LoadBalancer.php',
348349 'LoadMonitor' => 'includes/db/LoadMonitor.php',
349350 'LoadMonitor_MySQL' => 'includes/db/LoadMonitor.php',
Index: trunk/phase3/includes/db/DatabaseSqlite.php
@@ -418,6 +418,14 @@
419419 return $s;
420420 }
421421
 422+ function buildLike() {
 423+ $params = func_get_args();
 424+ if ( count( $params ) > 0 && is_array( $params[0] ) ) {
 425+ $params = $params[0];
 426+ }
 427+ return parent::buildLike( $params ) . "ESCAPE '\' ";
 428+ }
 429+
422430 /**
423431 * How lagged is this slave?
424432 */
Index: trunk/phase3/includes/db/Database.php
@@ -1490,7 +1490,9 @@
14911491 }
14921492
14931493 /**
1494 - * Escape string for safe LIKE usage
 1494+ * Escape string for safe LIKE usage.
 1495+ * WARNING: you should almost never use this function directly,
 1496+ * instead use buildLike() that escapes everything automatically
14951497 */
14961498 function escapeLike( $s ) {
14971499 $s = str_replace( '\\', '\\\\', $s );
@@ -1500,6 +1502,48 @@
15011503 }
15021504
15031505 /**
 1506+ * LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match
 1507+ * containing either string literals that will be escaped or tokens returned by anyChar() or anyString().
 1508+ * Alternatively, the function could be provided with an array of aforementioned parameters.
 1509+ *
 1510+ * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns a LIKE clause that searches
 1511+ * for subpages of 'My page title'.
 1512+ * Alternatively: $pattern = array( 'My_page_title/', $dbr->anyString() ); $query .= $dbr->buildLike( $pattern );
 1513+ *
 1514+ * @ return String: fully built LIKE statement
 1515+ */
 1516+ function buildLike() {
 1517+ $params = func_get_args();
 1518+ if (count($params) > 0 && is_array($params[0])) {
 1519+ $params = $params[0];
 1520+ }
 1521+
 1522+ $s = '';
 1523+ foreach( $params as $value) {
 1524+ if( $value instanceof LikeMatch ) {
 1525+ $s .= $value->toString();
 1526+ } else {
 1527+ $s .= $this->escapeLike( $value );
 1528+ }
 1529+ }
 1530+ return " LIKE '" . $s . "' ";
 1531+ }
 1532+
 1533+ /**
 1534+ * Returns a token for buildLike() that denotes a '_' to be used in a LIKE query
 1535+ */
 1536+ function anyChar() {
 1537+ return new LikeMatch( '_' );
 1538+ }
 1539+
 1540+ /**
 1541+ * Returns a token for buildLike() that denotes a '%' to be used in a LIKE query
 1542+ */
 1543+ function anyString() {
 1544+ return new LikeMatch( '%' );
 1545+ }
 1546+
 1547+ /**
15041548 * Returns an appropriately quoted sequence value for inserting a new row.
15051549 * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
15061550 * subclass will return an integer, and save the value for insertId()
@@ -2779,3 +2823,19 @@
27802824 return $this->current() !== false;
27812825 }
27822826 }
 2827+
 2828+/**
 2829+ * Used by DatabaseBase::buildLike() to represent characters that have special meaning in SQL LIKE clauses
 2830+ * and thus need no escaping. Don't instantiate it manually, use Database::anyChar() and anyString() instead.
 2831+ */
 2832+class LikeMatch {
 2833+ private $str;
 2834+
 2835+ public function __construct( $s ) {
 2836+ $this->str = $s;
 2837+ }
 2838+
 2839+ public function toString() {
 2840+ return $this->str;
 2841+ }
 2842+}
\ No newline at end of file
Index: trunk/phase3/includes/api/ApiQueryAllUsers.php
@@ -61,7 +61,7 @@
6262 $this->addWhere('u1.user_name >= ' . $db->addQuotes($this->keyToTitle($params['from'])));
6363
6464 if (!is_null($params['prefix']))
65 - $this->addWhere('u1.user_name LIKE "' . $db->escapeLike($this->keyToTitle( $params['prefix'])) . '%"');
 65+ $this->addWhere('u1.user_name' . $db->buildLike($this->keyToTitle($params['prefix']), $db->anyString()));
6666
6767 if (!is_null($params['group'])) {
6868 // Filter only users that belong to a given group
Index: trunk/phase3/includes/api/ApiQueryAllimages.php
@@ -76,7 +76,7 @@
7777 $from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from']));
7878 $this->addWhereRange('img_name', $dir, $from, null);
7979 if (isset ($params['prefix']))
80 - $this->addWhere("img_name LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'");
 80+ $this->addWhere('img_name' . $db->buildLike( $this->titlePartToKey($params['prefix']), $db->anyString() ) );
8181
8282 if (isset ($params['minsize'])) {
8383 $this->addWhere('img_size>=' . intval($params['minsize']));
Index: trunk/phase3/includes/api/ApiQueryAllLinks.php
@@ -84,7 +84,7 @@
8585 if (!is_null($params['from']))
8686 $this->addWhere('pl_title>=' . $db->addQuotes($this->titlePartToKey($params['from'])));
8787 if (isset ($params['prefix']))
88 - $this->addWhere("pl_title LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'");
 88+ $this->addWhere('pl_title' . $db->buildLike( $this->titlePartToKey($params['prefix']), $db->anyString() ) );
8989
9090 $this->addFields(array (
9191 'pl_title',
Index: trunk/phase3/includes/api/ApiQueryAllpages.php
@@ -65,7 +65,7 @@
6666 $from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from']));
6767 $this->addWhereRange('page_title', $dir, $from, null);
6868 if (isset ($params['prefix']))
69 - $this->addWhere("page_title LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'");
 69+ $this->addWhere('page_title' . $db->buildLike($this->titlePartToKey($params['prefix']), $db->->anyString()));
7070
7171 if (is_null($resultPageSet)) {
7272 $selectFields = array (
Index: trunk/phase3/includes/api/ApiQueryBlocks.php
@@ -109,8 +109,10 @@
110110 else
111111 $lower = $upper = IP::toHex($params['ip']);
112112 $prefix = substr($lower, 0, 4);
 113+
 114+ $db = $this->getDB();
113115 $this->addWhere(array(
114 - "ipb_range_start LIKE '$prefix%'",
 116+ 'ipb_range_start' . $db->buildLike($prefix, $db->anyString()),
115117 "ipb_range_start <= '$lower'",
116118 "ipb_range_end >= '$upper'"
117119 ));
Index: trunk/phase3/includes/api/ApiQueryUserContributions.php
@@ -165,7 +165,7 @@
166166 $this->addWhere($this->getDB()->bitAnd('rev_deleted',Revision::DELETED_USER) . ' = 0');
167167 // We only want pages by the specified users.
168168 if($this->prefixMode)
169 - $this->addWhere("rev_user_text LIKE '" . $this->getDB()->escapeLike($this->userprefix) . "%'");
 169+ $this->addWhere('rev_user_text' . $this->getDB()->buildLike($this->userprefix, $this->getDB()->anyString()));
170170 else
171171 $this->addWhereFld('rev_user_text', $this->usernames);
172172 // ... and in the specified timeframe.
Index: trunk/phase3/includes/api/ApiQueryAllCategories.php
@@ -60,7 +60,7 @@
6161 $from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from']));
6262 $this->addWhereRange('cat_title', $dir, $from, null);
6363 if (isset ($params['prefix']))
64 - $this->addWhere("cat_title LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'");
 64+ $this->addWhere('cat_title' . $db->buildLike( $this->titlePartToKey($params['prefix']), $db->anyString() ) );
6565
6666 $this->addOption('LIMIT', $params['limit']+1);
6767 $this->addOption('ORDER BY', 'cat_title' . ($params['dir'] == 'descending' ? ' DESC' : ''));
Index: trunk/phase3/includes/api/ApiQueryExtLinksUsage.php
@@ -77,14 +77,15 @@
7878 if(is_null($protocol))
7979 $protocol = 'http://';
8080
81 - $likeQuery = LinkFilter::makeLike($query, $protocol);
 81+ $likeQuery = LinkFilter::makeLikeArray($query, $protocol);
8282 if (!$likeQuery)
8383 $this->dieUsage('Invalid query', 'bad_query');
84 - $likeQuery = substr($likeQuery, 0, strpos($likeQuery,'%')+1);
85 - $this->addWhere('el_index LIKE ' . $db->addQuotes( $likeQuery ));
 84+
 85+ $likeQuery = LinkFilter::keepOneWildcard($likeQuery);
 86+ $this->addWhere('el_index ' . $db->buildLike( $likeQuery ));
8687 }
8788 else if(!is_null($protocol))
88 - $this->addWhere('el_index LIKE ' . $db->addQuotes( "$protocol%" ));
 89+ $this->addWhere('el_index ' . $db->buildLike( "$protocol", $db->anyString() ));
8990
9091 $prop = array_flip($params['prop']);
9192 $fld_ids = isset($prop['ids']);
Index: trunk/phase3/includes/MessageCache.php
@@ -318,12 +318,11 @@
319319 # database or in code.
320320 if ( $code !== $wgContLanguageCode ) {
321321 # Messages for particular language
322 - $escapedCode = $dbr->escapeLike( $code );
323 - $conds[] = "page_title like '%%/$escapedCode'";
 322+ $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), "/$code" );
324323 } else {
325324 # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
326325 # other than language code.
327 - $conds[] = "page_title not like '%%/%%'";
 326+ $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
328327 }
329328 }
330329
Index: trunk/phase3/includes/LogEventsList.php
@@ -854,6 +854,8 @@
855855
856856 $this->title = $title->getPrefixedText();
857857 $ns = $title->getNamespace();
 858+ $db = $this->mDb;
 859+
858860 # Using the (log_namespace, log_title, log_timestamp) index with a
859861 # range scan (LIKE) on the first two parts, instead of simple equality,
860862 # makes it unusable for sorting. Sorted retrieval using another index
@@ -866,10 +868,8 @@
867869 # log entries for even the busiest pages, so it can be safely scanned
868870 # in full to satisfy an impossible condition on user or similar.
869871 if( $pattern && !$wgMiserMode ) {
870 - # use escapeLike to avoid expensive search patterns like 't%st%'
871 - $safetitle = $this->mDb->escapeLike( $title->getDBkey() );
872872 $this->mConds['log_namespace'] = $ns;
873 - $this->mConds[] = "log_title LIKE '$safetitle%'";
 873+ $this->mConds[] = 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() );
874874 $this->pattern = $pattern;
875875 } else {
876876 $this->mConds['log_namespace'] = $ns;
@@ -877,9 +877,9 @@
878878 }
879879 // Paranoia: avoid brute force searches (bug 17342)
880880 if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
881 - $this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::DELETED_ACTION) . ' = 0';
 881+ $this->mConds[] = $db->bitAnd('log_deleted', LogPage::DELETED_ACTION) . ' = 0';
882882 } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
883 - $this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::SUPPRESSED_ACTION) .
 883+ $this->mConds[] = $db->bitAnd('log_deleted', LogPage::SUPPRESSED_ACTION) .
884884 ' != ' . LogPage::SUPPRESSED_ACTION;
885885 }
886886 }
Index: trunk/phase3/includes/LinkFilter.php
@@ -49,8 +49,10 @@
5050 * @static
5151 * @param $filterEntry String: domainparts
5252 * @param $prot String: protocol
 53+ * @deprecated Use makeLikeArray() and pass result to Database::buildLike() instead
5354 */
5455 public static function makeLike( $filterEntry , $prot = 'http://' ) {
 56+ wfDeprecated( __METHOD__ );
5557 $db = wfGetDB( DB_MASTER );
5658 if ( substr( $filterEntry, 0, 2 ) == '*.' ) {
5759 $subdomains = true;
@@ -105,4 +107,99 @@
106108 }
107109 return $like;
108110 }
 111+
 112+ /**
 113+ * Make an array to be used for calls to Database::like(), which will match the specified
 114+ * string. There are several kinds of filter entry:
 115+ * *.domain.com - Produces http://com.domain.%, matches domain.com
 116+ * and www.domain.com
 117+ * domain.com - Produces http://com.domain./%, matches domain.com
 118+ * or domain.com/ but not www.domain.com
 119+ * *.domain.com/x - Produces http://com.domain.%/x%, matches
 120+ * www.domain.com/xy
 121+ * domain.com/x - Produces http://com.domain./x%, matches
 122+ * domain.com/xy but not www.domain.com/xy
 123+ *
 124+ * Asterisks in any other location are considered invalid.
 125+ *
 126+ * @static
 127+ * @param $filterEntry String: domainparts
 128+ * @param $prot String: protocol
 129+ */
 130+ public static function makeLikeArray( $filterEntry , $prot = 'http://' ) {
 131+ $db = wfGetDB( DB_MASTER );
 132+ if ( substr( $filterEntry, 0, 2 ) == '*.' ) {
 133+ $subdomains = true;
 134+ $filterEntry = substr( $filterEntry, 2 );
 135+ if ( $filterEntry == '' ) {
 136+ // We don't want to make a clause that will match everything,
 137+ // that could be dangerous
 138+ return false;
 139+ }
 140+ } else {
 141+ $subdomains = false;
 142+ }
 143+ // No stray asterisks, that could cause confusion
 144+ // It's not simple or efficient to handle it properly so we don't
 145+ // handle it at all.
 146+ if ( strpos( $filterEntry, '*' ) !== false ) {
 147+ return false;
 148+ }
 149+ $slash = strpos( $filterEntry, '/' );
 150+ if ( $slash !== false ) {
 151+ $path = substr( $filterEntry, $slash );
 152+ $host = substr( $filterEntry, 0, $slash );
 153+ } else {
 154+ $path = '/';
 155+ $host = $filterEntry;
 156+ }
 157+ // Reverse the labels in the hostname, convert to lower case
 158+ // For emails reverse domainpart only
 159+ if ( $prot == 'mailto:' && strpos($host, '@') ) {
 160+ // complete email adress
 161+ $mailparts = explode( '@', $host );
 162+ $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
 163+ $host = $domainpart . '@' . $mailparts[0];
 164+ $like = array( "$prot$host", $db->anyString() );
 165+ } elseif ( $prot == 'mailto:' ) {
 166+ // domainpart of email adress only. do not add '.'
 167+ $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
 168+ $like = array( "$prot$host", $db->anyString() );
 169+ } else {
 170+ $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
 171+ if ( substr( $host, -1, 1 ) !== '.' ) {
 172+ $host .= '.';
 173+ }
 174+ $like = array( "$prot$host" );
 175+
 176+ if ( $subdomains ) {
 177+ $like[] = $db->anyString();
 178+ }
 179+ if ( !$subdomains || $path !== '/' ) {
 180+ $like[] = $path;
 181+ $like[] = $db->anyString();
 182+ }
 183+ }
 184+ return $like;
 185+ }
 186+
 187+ /**
 188+ * Filters an array returned by makeLikeArray(), removing everything past first pattern placeholder.
 189+ * @static
 190+ * @param $arr array: array to filter
 191+ * @return filtered array
 192+ */
 193+ public static function keepOneWildcard( $arr ) {
 194+ if( !is_array( $arr ) ) {
 195+ return $arr;
 196+ }
 197+
 198+ foreach( $arr as $key => $value ) {
 199+ if ( $value instanceof LikeMatch ) {
 200+ return array_slice( $arr, 0, $key + 1 );
 201+ }
 202+ }
 203+
 204+ return $arr;
 205+ }
109206 }
Index: trunk/phase3/includes/filerepo/LocalRepo.php
@@ -49,7 +49,7 @@
5050 $ext = File::normalizeExtension($ext);
5151 $inuse = $dbw->selectField( 'oldimage', '1',
5252 array( 'oi_sha1' => $sha1,
53 - "oi_archive_name LIKE '%.{$ext}'",
 53+ 'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ),
5454 $dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE ),
5555 __METHOD__, array( 'FOR UPDATE' ) );
5656 }

Follow-up revisions

RevisionCommit summaryAuthorDate
r57995Fix syntax errors from r57989nikerabbit06:18, 22 October 2009
r60162Fixup for r57989: removed artifacts of the previous version of my patch, impr...maxsem15:39, 17 December 2009
r793511.17: Fix fatal error due to undefined $dbr introduced in r57989. This was fi...catrope16:56, 31 December 2010

Comments

#Comment by Tim Starling (talk | contribs)   01:18, 17 December 2009

You left a couple of MATCH_STRING references in maintenance/storage/compressOld.inc, so that file is broken.

LinkFilter::makeLike() could easily be implemented by calling LinkFilter::makeLikeArray() and processing the result, at least for MySQL compatibility which is all you're going to get anyway. This would remove the code duplication and so make this code easier to maintain.

The origin of the separating space, at the start of the return value of buildLike() instead of in the caller, is a bit weird but I guess I can live with it.

Otherwise looks good.

#Comment by MaxSem (talk | contribs)   15:41, 17 December 2009

The origin of the separating space, at the start of the return value of buildLike() instead of in the caller, is a bit weird

Aryeh proposed to add it in order to reduce the possibility of code that generates SQL without a single space.

#Comment by Tim Starling (talk | contribs)   02:51, 18 December 2009

Yeah, but as long as there are some functions in Database which don't follow the same convention, you risk tricking people into making syntax errors by following your example and assuming that other similar functions produce a leading space. I think I would have been inclined to put a space in both places. But like I say, not a big deal.

Status & tagging log