Index: trunk/phase3/includes/api/ApiQueryBase.php |
— | — | @@ -246,14 +246,21 @@ |
247 | 247 | * Execute a SELECT query based on the values in the internal arrays |
248 | 248 | * @param $method string Function the query should be attributed to. |
249 | 249 | * You should usually use __METHOD__ here |
| 250 | + * @param $extraQuery array Query data to add but not store in the object |
| 251 | + * Format is array( 'tables' => ..., 'fields' => ..., 'where' => ..., 'options' => ..., 'join_conds' => ... ) |
250 | 252 | * @return ResultWrapper |
251 | 253 | */ |
252 | | - protected function select( $method ) { |
| 254 | + protected function select( $method, $extraQuery = array() ) { |
| 255 | + // Merge $this->tables with $extraQuery['tables'], $this->fields with $extraQuery['fields'], etc. |
| 256 | + foreach ( array( 'tables', 'fields', 'where', 'options', 'join_conds' ) as $var ) { |
| 257 | + $$var = array_merge( $this->{$var}, isset( $extraQuery[$var] ) ? (array)$extraQuery[$var] : array() ); |
| 258 | + } |
| 259 | + |
253 | 260 | // getDB has its own profileDBIn/Out calls |
254 | 261 | $db = $this->getDB(); |
255 | 262 | |
256 | 263 | $this->profileDBIn(); |
257 | | - $res = $db->select( $this->tables, $this->fields, $this->where, $method, $this->options, $this->join_conds ); |
| 264 | + $res = $db->select( $tables, $fields, $where, $method, $options, $join_conds ); |
258 | 265 | $this->profileDBOut(); |
259 | 266 | |
260 | 267 | return $res; |
Index: trunk/phase3/includes/api/ApiQueryCategoryMembers.php |
— | — | @@ -99,7 +99,8 @@ |
100 | 100 | $this->addTables( array( 'page', 'categorylinks' ) ); // must be in this order for 'USE INDEX' |
101 | 101 | |
102 | 102 | $this->addWhereFld( 'cl_to', $categoryTitle->getDBkey() ); |
103 | | - $this->addWhereFld( 'cl_type', $params['type'] ); |
| 103 | + $queryTypes = $params['type']; |
| 104 | + $contWhere = false; |
104 | 105 | |
105 | 106 | // Scanning large datasets for rare categories sucks, and I already told |
106 | 107 | // how to have efficient subcategory access :-) ~~~~ (oh well, domas) |
— | — | @@ -129,20 +130,22 @@ |
130 | 131 | 'by the previous query', '_badcontinue' |
131 | 132 | ); |
132 | 133 | } |
133 | | - $escType = $this->getDB()->addQuotes( $cont[0] ); |
| 134 | + |
| 135 | + // Remove the types to skip from $queryTypes |
| 136 | + $contTypeIndex = array_search( $cont[0], $queryTypes ); |
| 137 | + $queryTypes = array_slice( $queryTypes, $contTypeIndex ); |
| 138 | + |
| 139 | + // Add a WHERE clause for sortkey and from |
134 | 140 | $from = intval( $cont[1] ); |
135 | 141 | $escSortkey = $this->getDB()->addQuotes( $cont[2] ); |
136 | 142 | $op = $dir == 'newer' ? '>' : '<'; |
137 | | - $this->addWhere( "cl_type $op $escType OR " . |
138 | | - "(cl_type = $escType AND " . |
139 | | - "(cl_sortkey $op $escSortkey OR " . |
| 143 | + // $contWhere is used further down |
| 144 | + $contWhere = "cl_sortkey $op $escSortkey OR " . |
140 | 145 | "(cl_sortkey = $escSortkey AND " . |
141 | | - "cl_from $op= $from)))" |
142 | | - ); |
| 146 | + "cl_from $op= $from)"; |
143 | 147 | |
144 | 148 | } else { |
145 | | - // The below produces ORDER BY cl_type, cl_sortkey, cl_from, possibly with DESC added to each of them |
146 | | - $this->addWhereRange( 'cl_type', $dir, null, null ); |
| 149 | + // The below produces ORDER BY cl_sortkey, cl_from, possibly with DESC added to each of them |
147 | 150 | $this->addWhereRange( 'cl_sortkey', |
148 | 151 | $dir, |
149 | 152 | $params['startsortkey'], |
— | — | @@ -157,9 +160,29 @@ |
158 | 161 | $limit = $params['limit']; |
159 | 162 | $this->addOption( 'LIMIT', $limit + 1 ); |
160 | 163 | |
| 164 | + // Run a separate SELECT query for each value of cl_type. |
| 165 | + // This is needed because cl_type is an enum, and MySQL has |
| 166 | + // inconsistencies between ORDER BY cl_type and |
| 167 | + // WHERE cl_type >= 'foo' making proper paging impossible |
| 168 | + // and unindexed. |
| 169 | + $rows = array(); |
| 170 | + $first = true; |
| 171 | + foreach ( $queryTypes as $type ) { |
| 172 | + $extraConds = array( 'cl_type' => $type ); |
| 173 | + if ( $first && $contWhere ) { |
| 174 | + // Continuation condition. Only added to the |
| 175 | + // first query, otherwise we'll skip things |
| 176 | + $extraConds[] = $contWhere; |
| 177 | + } |
| 178 | + $res = $this->select( __METHOD__, array( 'where' => $extraConds ) ); |
| 179 | + $rows = array_merge( $rows, iterator_to_array( $res ) ); |
| 180 | + if ( count( $rows ) >= $limit + 1 ) { |
| 181 | + break; |
| 182 | + } |
| 183 | + $first = false; |
| 184 | + } |
161 | 185 | $count = 0; |
162 | | - $res = $this->select( __METHOD__ ); |
163 | | - foreach ( $res as $row ) { |
| 186 | + foreach ( $rows as $row ) { |
164 | 187 | if ( ++ $count > $limit ) { |
165 | 188 | // We've reached the one extra which shows that there are additional pages to be had. Stop here... |
166 | 189 | // TODO: Security issue - if the user has no right to view next title, it will still be shown |