Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -581,6 +581,7 @@ |
582 | 582 | dummy |
583 | 583 | * (bug 21161) Changing $wgCacheEpoch now always invalidates file cache |
584 | 584 | * (bug 20268) Fixed row count estimation on SQLite backend |
| 585 | +* (bug 20275) Fixed LIKE queries on SQLite backend |
585 | 586 | |
586 | 587 | == API changes in 1.16 == |
587 | 588 | |
Index: trunk/phase3/maintenance/cleanupSpam.php |
— | — | @@ -40,7 +40,7 @@ |
41 | 41 | $wgUser->addToDatabase(); |
42 | 42 | } |
43 | 43 | $spec = $this->getArg(); |
44 | | - $like = LinkFilter::makeLike( $spec ); |
| 44 | + $like = LinkFilter::makeLikeArray( $spec ); |
45 | 45 | if ( !$like ) { |
46 | 46 | $this->error( "Not a valid hostname specification: $spec", true ); |
47 | 47 | } |
— | — | @@ -53,7 +53,7 @@ |
54 | 54 | $dbr = wfGetDB( DB_SLAVE, array(), $wikiID ); |
55 | 55 | |
56 | 56 | $count = $dbr->selectField( 'externallinks', 'COUNT(*)', |
57 | | - array( 'el_index LIKE ' . $dbr->addQuotes( $like ) ), __METHOD__ ); |
| 57 | + array( 'el_index' . $dbr->buildLike( $like ) ), __METHOD__ ); |
58 | 58 | if ( $count ) { |
59 | 59 | $found = true; |
60 | 60 | passthru( "php cleanupSpam.php --wiki='$wikiID' $spec | sed 's/^/$wikiID: /'" ); |
— | — | @@ -69,7 +69,7 @@ |
70 | 70 | |
71 | 71 | $dbr = wfGetDB( DB_SLAVE ); |
72 | 72 | $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__ ); |
74 | 74 | $count = $dbr->numRows( $res ); |
75 | 75 | $this->output( "Found $count articles containing $spec\n" ); |
76 | 76 | foreach ( $res as $row ) { |
Index: trunk/phase3/maintenance/storage/resolveStubs.php |
— | — | @@ -69,7 +69,7 @@ |
70 | 70 | |
71 | 71 | # Get the (maybe) external row |
72 | 72 | $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() ) ), |
74 | 74 | $fname |
75 | 75 | ); |
76 | 76 | |
Index: trunk/phase3/maintenance/storage/trackBlobs.php |
— | — | @@ -60,7 +60,7 @@ |
61 | 61 | if ( $this->textClause != '' ) { |
62 | 62 | $this->textClause .= ' OR '; |
63 | 63 | } |
64 | | - $this->textClause .= 'old_text LIKE ' . $dbr->addQuotes( $dbr->escapeLike( "DB://$cluster/" ) . '%' ); |
| 64 | + $this->textClause .= 'old_text' . $dbr->buildLike( "DB://$cluster/", $dbr->anyString() ); |
65 | 65 | } |
66 | 66 | } |
67 | 67 | return $this->textClause; |
— | — | @@ -99,7 +99,7 @@ |
100 | 100 | 'rev_id > ' . $dbr->addQuotes( $startId ), |
101 | 101 | 'rev_text_id=old_id', |
102 | 102 | $textClause, |
103 | | - "old_flags LIKE '%external%'", |
| 103 | + 'old_flags ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ), |
104 | 104 | ), |
105 | 105 | __METHOD__, |
106 | 106 | array( |
— | — | @@ -175,7 +175,7 @@ |
176 | 176 | array( |
177 | 177 | 'old_id>' . $dbr->addQuotes( $startId ), |
178 | 178 | $textClause, |
179 | | - "old_flags LIKE '%external%'", |
| 179 | + 'old_flags ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ), |
180 | 180 | 'bt_text_id IS NULL' |
181 | 181 | ), |
182 | 182 | __METHOD__, |
Index: trunk/phase3/maintenance/storage/compressOld.inc |
— | — | @@ -105,7 +105,8 @@ |
106 | 106 | # overwriting bulk storage concat rows. Don't compress external references, because |
107 | 107 | # the script doesn't yet delete rows from external storage. |
108 | 108 | $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 ) ); |
110 | 111 | |
111 | 112 | if ( $beginDate ) { |
112 | 113 | if ( !preg_match( '/^\d{14}$/', $beginDate ) ) { |
Index: trunk/phase3/maintenance/storage/moveToExternal.php |
— | — | @@ -62,7 +62,7 @@ |
63 | 63 | $res = $dbr->select( 'text', array( 'old_id', 'old_flags', 'old_text' ), |
64 | 64 | array( |
65 | 65 | "old_id BETWEEN $blockStart AND $blockEnd", |
66 | | - "old_flags NOT LIKE '%external%'", |
| 66 | + 'old_flags NOT ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ), |
67 | 67 | ), $fname ); |
68 | 68 | while ( $row = $dbr->fetchObject( $res ) ) { |
69 | 69 | # Resolve stubs |
Index: trunk/phase3/maintenance/namespaceDupes.php |
— | — | @@ -197,7 +197,6 @@ |
198 | 198 | $table = $this->db->tableName( $page ); |
199 | 199 | |
200 | 200 | $prefix = $this->db->strencode( $name ); |
201 | | - $likeprefix = str_replace( '_', '\\_', $prefix); |
202 | 201 | $encNamespace = $this->db->addQuotes( $ns ); |
203 | 202 | |
204 | 203 | $titleSql = "TRIM(LEADING '$prefix:' FROM {$page}_title)"; |
— | — | @@ -212,7 +211,7 @@ |
213 | 212 | $titleSql AS title |
214 | 213 | FROM {$table} |
215 | 214 | WHERE {$page}_namespace=0 |
216 | | - AND {$page}_title LIKE '$likeprefix:%'"; |
| 215 | + AND {$page}_title " . $this->db->buildLike( $name . ':', $this-db->anyString() ); |
217 | 216 | |
218 | 217 | $result = $this->db->query( $sql, __METHOD__ ); |
219 | 218 | |
Index: trunk/phase3/maintenance/deleteSelfExternals.php |
— | — | @@ -7,8 +7,8 @@ |
8 | 8 | while (1) { |
9 | 9 | wfWaitForSlaves( 2 ); |
10 | 10 | $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 ); |
13 | 13 | print "Deleting a batch\n"; |
14 | 14 | $db->query($q); |
15 | 15 | if (!$db->affectedRows()) exit(0); |
Index: trunk/phase3/includes/Title.php |
— | — | @@ -1663,8 +1663,7 @@ |
1664 | 1664 | |
1665 | 1665 | $dbr = wfGetDB( DB_SLAVE ); |
1666 | 1666 | $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() ); |
1669 | 1668 | $options = array(); |
1670 | 1669 | if( $limit > -1 ) |
1671 | 1670 | $options['LIMIT'] = $limit; |
Index: trunk/phase3/includes/Block.php |
— | — | @@ -286,7 +286,7 @@ |
287 | 287 | $options = array(); |
288 | 288 | $db =& $this->getDBOptions( $options ); |
289 | 289 | $conds = array( |
290 | | - "ipb_range_start LIKE '$range%'", |
| 290 | + 'ipb_range_start' . $db->buildLike( $range, $db->anyString() ), |
291 | 291 | "ipb_range_start <= '$iaddr'", |
292 | 292 | "ipb_range_end >= '$iaddr'" |
293 | 293 | ); |
Index: trunk/phase3/includes/specials/SpecialLinkSearch.php |
— | — | @@ -96,11 +96,11 @@ |
97 | 97 | */ |
98 | 98 | static function mungeQuery( $query , $prot ) { |
99 | 99 | $field = 'el_index'; |
100 | | - $rv = LinkFilter::makeLike( $query , $prot ); |
| 100 | + $rv = LinkFilter::makeLikeArray( $query , $prot ); |
101 | 101 | if ($rv === false) { |
102 | 102 | //makeLike doesn't handle wildcard in IP, so we'll have to munge here. |
103 | 103 | 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() ); |
105 | 105 | $field = 'el_to'; |
106 | 106 | } |
107 | 107 | } |
— | — | @@ -125,8 +125,8 @@ |
126 | 126 | |
127 | 127 | /* strip everything past first wildcard, so that index-based-only lookup would be done */ |
128 | 128 | 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 ); |
131 | 131 | |
132 | 132 | $encSQL = ''; |
133 | 133 | if ( isset ($this->mNs) && !$wgMiserMode ) |
— | — | @@ -144,7 +144,7 @@ |
145 | 145 | $externallinks $use_index |
146 | 146 | WHERE |
147 | 147 | page_id=el_from |
148 | | - AND $clause LIKE $encSearch |
| 148 | + AND $clause $like |
149 | 149 | $encSQL"; |
150 | 150 | } |
151 | 151 | |
Index: trunk/phase3/includes/specials/SpecialNewimages.php |
— | — | @@ -69,8 +69,7 @@ |
70 | 70 | if ( $wpIlMatch != '' && !$wgMiserMode) { |
71 | 71 | $nt = Title::newFromUrl( $wpIlMatch ); |
72 | 72 | 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() ); |
75 | 74 | $searchpar['wpIlMatch'] = $wpIlMatch; |
76 | 75 | } |
77 | 76 | } |
Index: trunk/phase3/includes/specials/SpecialPrefixindex.php |
— | — | @@ -115,7 +115,7 @@ |
116 | 116 | array( 'page_namespace', 'page_title', 'page_is_redirect' ), |
117 | 117 | array( |
118 | 118 | 'page_namespace' => $namespace, |
119 | | - 'page_title LIKE \'' . $dbr->escapeLike( $prefixKey ) .'%\'', |
| 119 | + 'page_title' . $dbr->buildLike( $prefixKey, $dbr->anyString() ), |
120 | 120 | 'page_title >= ' . $dbr->addQuotes( $fromKey ), |
121 | 121 | ), |
122 | 122 | __METHOD__, |
Index: trunk/phase3/includes/specials/SpecialWithoutinterwiki.php |
— | — | @@ -53,7 +53,7 @@ |
54 | 54 | function getSQL() { |
55 | 55 | $dbr = wfGetDB( DB_SLAVE ); |
56 | 56 | 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() ) : ''; |
58 | 58 | return |
59 | 59 | "SELECT 'Withoutinterwiki' AS type, |
60 | 60 | page_namespace AS namespace, |
Index: trunk/phase3/includes/specials/SpecialMovepage.php |
— | — | @@ -416,7 +416,7 @@ |
417 | 417 | ) |
418 | 418 | ) ) { |
419 | 419 | $conds = array( |
420 | | - 'page_title LIKE '.$dbr->addQuotes( $dbr->escapeLike( $ot->getDBkey() ) . '/%' ) |
| 420 | + 'page_title' . $dbr->buildLike( $ot->getDBkey() . '/', $dbr->anyString() ) |
421 | 421 | .' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() ) |
422 | 422 | ); |
423 | 423 | $conds['page_namespace'] = array(); |
Index: trunk/phase3/includes/specials/SpecialListfiles.php |
— | — | @@ -37,10 +37,8 @@ |
38 | 38 | $nt = Title::newFromUrl( $search ); |
39 | 39 | if( $nt ) { |
40 | 40 | $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() ) ); |
45 | 43 | } |
46 | 44 | } |
47 | 45 | |
Index: trunk/phase3/includes/specials/SpecialUndelete.php |
— | — | @@ -58,16 +58,15 @@ |
59 | 59 | $title = Title::newFromText( $prefix ); |
60 | 60 | if( $title ) { |
61 | 61 | $ns = $title->getNamespace(); |
62 | | - $encPrefix = $dbr->escapeLike( $title->getDBkey() ); |
| 62 | + $prefix = $title->getDBkey(); |
63 | 63 | } else { |
64 | 64 | // Prolly won't work too good |
65 | 65 | // @todo handle bare namespace names cleanly? |
66 | 66 | $ns = 0; |
67 | | - $encPrefix = $dbr->escapeLike( $prefix ); |
68 | 67 | } |
69 | 68 | $conds = array( |
70 | 69 | 'ar_namespace' => $ns, |
71 | | - "ar_title LIKE '$encPrefix%'", |
| 70 | + 'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ), |
72 | 71 | ); |
73 | 72 | return self::listPages( $dbr, $conds ); |
74 | 73 | } |
Index: trunk/phase3/includes/specials/SpecialIpblocklist.php |
— | — | @@ -262,10 +262,9 @@ |
263 | 263 | // Fixme -- encapsulate this sort of query-building. |
264 | 264 | $dbr = wfGetDB( DB_SLAVE ); |
265 | 265 | $encIp = $dbr->addQuotes( IP::sanitizeIP($this->ip) ); |
266 | | - $encRange = $dbr->addQuotes( "$range%" ); |
267 | 266 | $encAddr = $dbr->addQuotes( $iaddr ); |
268 | 267 | $conds[] = "(ipb_address = $encIp) OR |
269 | | - (ipb_range_start LIKE $encRange AND |
| 268 | + (ipb_range_start" . $dbr->buildLike( $range, $dbr->anyString() ) . " AND |
270 | 269 | ipb_range_start <= $encAddr |
271 | 270 | AND ipb_range_end >= $encAddr)"; |
272 | 271 | } else { |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -343,6 +343,7 @@ |
344 | 344 | 'LBFactory' => 'includes/db/LBFactory.php', |
345 | 345 | 'LBFactory_Multi' => 'includes/db/LBFactory_Multi.php', |
346 | 346 | 'LBFactory_Simple' => 'includes/db/LBFactory.php', |
| 347 | + 'LikeMatch' => 'includes/db/Database.php', |
347 | 348 | 'LoadBalancer' => 'includes/db/LoadBalancer.php', |
348 | 349 | 'LoadMonitor' => 'includes/db/LoadMonitor.php', |
349 | 350 | 'LoadMonitor_MySQL' => 'includes/db/LoadMonitor.php', |
Index: trunk/phase3/includes/db/DatabaseSqlite.php |
— | — | @@ -418,6 +418,14 @@ |
419 | 419 | return $s; |
420 | 420 | } |
421 | 421 | |
| 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 | + |
422 | 430 | /** |
423 | 431 | * How lagged is this slave? |
424 | 432 | */ |
Index: trunk/phase3/includes/db/Database.php |
— | — | @@ -1490,7 +1490,9 @@ |
1491 | 1491 | } |
1492 | 1492 | |
1493 | 1493 | /** |
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 |
1495 | 1497 | */ |
1496 | 1498 | function escapeLike( $s ) { |
1497 | 1499 | $s = str_replace( '\\', '\\\\', $s ); |
— | — | @@ -1500,6 +1502,48 @@ |
1501 | 1503 | } |
1502 | 1504 | |
1503 | 1505 | /** |
| 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 | + /** |
1504 | 1548 | * Returns an appropriately quoted sequence value for inserting a new row. |
1505 | 1549 | * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL |
1506 | 1550 | * subclass will return an integer, and save the value for insertId() |
— | — | @@ -2779,3 +2823,19 @@ |
2780 | 2824 | return $this->current() !== false; |
2781 | 2825 | } |
2782 | 2826 | } |
| 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 @@ |
62 | 62 | $this->addWhere('u1.user_name >= ' . $db->addQuotes($this->keyToTitle($params['from']))); |
63 | 63 | |
64 | 64 | 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())); |
66 | 66 | |
67 | 67 | if (!is_null($params['group'])) { |
68 | 68 | // Filter only users that belong to a given group |
Index: trunk/phase3/includes/api/ApiQueryAllimages.php |
— | — | @@ -76,7 +76,7 @@ |
77 | 77 | $from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from'])); |
78 | 78 | $this->addWhereRange('img_name', $dir, $from, null); |
79 | 79 | 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() ) ); |
81 | 81 | |
82 | 82 | if (isset ($params['minsize'])) { |
83 | 83 | $this->addWhere('img_size>=' . intval($params['minsize'])); |
Index: trunk/phase3/includes/api/ApiQueryAllLinks.php |
— | — | @@ -84,7 +84,7 @@ |
85 | 85 | if (!is_null($params['from'])) |
86 | 86 | $this->addWhere('pl_title>=' . $db->addQuotes($this->titlePartToKey($params['from']))); |
87 | 87 | 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() ) ); |
89 | 89 | |
90 | 90 | $this->addFields(array ( |
91 | 91 | 'pl_title', |
Index: trunk/phase3/includes/api/ApiQueryAllpages.php |
— | — | @@ -65,7 +65,7 @@ |
66 | 66 | $from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from'])); |
67 | 67 | $this->addWhereRange('page_title', $dir, $from, null); |
68 | 68 | 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())); |
70 | 70 | |
71 | 71 | if (is_null($resultPageSet)) { |
72 | 72 | $selectFields = array ( |
Index: trunk/phase3/includes/api/ApiQueryBlocks.php |
— | — | @@ -109,8 +109,10 @@ |
110 | 110 | else |
111 | 111 | $lower = $upper = IP::toHex($params['ip']); |
112 | 112 | $prefix = substr($lower, 0, 4); |
| 113 | + |
| 114 | + $db = $this->getDB(); |
113 | 115 | $this->addWhere(array( |
114 | | - "ipb_range_start LIKE '$prefix%'", |
| 116 | + 'ipb_range_start' . $db->buildLike($prefix, $db->anyString()), |
115 | 117 | "ipb_range_start <= '$lower'", |
116 | 118 | "ipb_range_end >= '$upper'" |
117 | 119 | )); |
Index: trunk/phase3/includes/api/ApiQueryUserContributions.php |
— | — | @@ -165,7 +165,7 @@ |
166 | 166 | $this->addWhere($this->getDB()->bitAnd('rev_deleted',Revision::DELETED_USER) . ' = 0'); |
167 | 167 | // We only want pages by the specified users. |
168 | 168 | 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())); |
170 | 170 | else |
171 | 171 | $this->addWhereFld('rev_user_text', $this->usernames); |
172 | 172 | // ... and in the specified timeframe. |
Index: trunk/phase3/includes/api/ApiQueryAllCategories.php |
— | — | @@ -60,7 +60,7 @@ |
61 | 61 | $from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from'])); |
62 | 62 | $this->addWhereRange('cat_title', $dir, $from, null); |
63 | 63 | 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() ) ); |
65 | 65 | |
66 | 66 | $this->addOption('LIMIT', $params['limit']+1); |
67 | 67 | $this->addOption('ORDER BY', 'cat_title' . ($params['dir'] == 'descending' ? ' DESC' : '')); |
Index: trunk/phase3/includes/api/ApiQueryExtLinksUsage.php |
— | — | @@ -77,14 +77,15 @@ |
78 | 78 | if(is_null($protocol)) |
79 | 79 | $protocol = 'http://'; |
80 | 80 | |
81 | | - $likeQuery = LinkFilter::makeLike($query, $protocol); |
| 81 | + $likeQuery = LinkFilter::makeLikeArray($query, $protocol); |
82 | 82 | if (!$likeQuery) |
83 | 83 | $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 )); |
86 | 87 | } |
87 | 88 | else if(!is_null($protocol)) |
88 | | - $this->addWhere('el_index LIKE ' . $db->addQuotes( "$protocol%" )); |
| 89 | + $this->addWhere('el_index ' . $db->buildLike( "$protocol", $db->anyString() )); |
89 | 90 | |
90 | 91 | $prop = array_flip($params['prop']); |
91 | 92 | $fld_ids = isset($prop['ids']); |
Index: trunk/phase3/includes/MessageCache.php |
— | — | @@ -318,12 +318,11 @@ |
319 | 319 | # database or in code. |
320 | 320 | if ( $code !== $wgContLanguageCode ) { |
321 | 321 | # Messages for particular language |
322 | | - $escapedCode = $dbr->escapeLike( $code ); |
323 | | - $conds[] = "page_title like '%%/$escapedCode'"; |
| 322 | + $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), "/$code" ); |
324 | 323 | } else { |
325 | 324 | # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses |
326 | 325 | # other than language code. |
327 | | - $conds[] = "page_title not like '%%/%%'"; |
| 326 | + $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() ); |
328 | 327 | } |
329 | 328 | } |
330 | 329 | |
Index: trunk/phase3/includes/LogEventsList.php |
— | — | @@ -854,6 +854,8 @@ |
855 | 855 | |
856 | 856 | $this->title = $title->getPrefixedText(); |
857 | 857 | $ns = $title->getNamespace(); |
| 858 | + $db = $this->mDb; |
| 859 | + |
858 | 860 | # Using the (log_namespace, log_title, log_timestamp) index with a |
859 | 861 | # range scan (LIKE) on the first two parts, instead of simple equality, |
860 | 862 | # makes it unusable for sorting. Sorted retrieval using another index |
— | — | @@ -866,10 +868,8 @@ |
867 | 869 | # log entries for even the busiest pages, so it can be safely scanned |
868 | 870 | # in full to satisfy an impossible condition on user or similar. |
869 | 871 | if( $pattern && !$wgMiserMode ) { |
870 | | - # use escapeLike to avoid expensive search patterns like 't%st%' |
871 | | - $safetitle = $this->mDb->escapeLike( $title->getDBkey() ); |
872 | 872 | $this->mConds['log_namespace'] = $ns; |
873 | | - $this->mConds[] = "log_title LIKE '$safetitle%'"; |
| 873 | + $this->mConds[] = 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() ); |
874 | 874 | $this->pattern = $pattern; |
875 | 875 | } else { |
876 | 876 | $this->mConds['log_namespace'] = $ns; |
— | — | @@ -877,9 +877,9 @@ |
878 | 878 | } |
879 | 879 | // Paranoia: avoid brute force searches (bug 17342) |
880 | 880 | 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'; |
882 | 882 | } 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) . |
884 | 884 | ' != ' . LogPage::SUPPRESSED_ACTION; |
885 | 885 | } |
886 | 886 | } |
Index: trunk/phase3/includes/LinkFilter.php |
— | — | @@ -49,8 +49,10 @@ |
50 | 50 | * @static |
51 | 51 | * @param $filterEntry String: domainparts |
52 | 52 | * @param $prot String: protocol |
| 53 | + * @deprecated Use makeLikeArray() and pass result to Database::buildLike() instead |
53 | 54 | */ |
54 | 55 | public static function makeLike( $filterEntry , $prot = 'http://' ) { |
| 56 | + wfDeprecated( __METHOD__ ); |
55 | 57 | $db = wfGetDB( DB_MASTER ); |
56 | 58 | if ( substr( $filterEntry, 0, 2 ) == '*.' ) { |
57 | 59 | $subdomains = true; |
— | — | @@ -105,4 +107,99 @@ |
106 | 108 | } |
107 | 109 | return $like; |
108 | 110 | } |
| 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 | + } |
109 | 206 | } |
Index: trunk/phase3/includes/filerepo/LocalRepo.php |
— | — | @@ -49,7 +49,7 @@ |
50 | 50 | $ext = File::normalizeExtension($ext); |
51 | 51 | $inuse = $dbw->selectField( 'oldimage', '1', |
52 | 52 | array( 'oi_sha1' => $sha1, |
53 | | - "oi_archive_name LIKE '%.{$ext}'", |
| 53 | + 'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ), |
54 | 54 | $dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE ), |
55 | 55 | __METHOD__, array( 'FOR UPDATE' ) ); |
56 | 56 | } |