From: martineznovo Date: Sun, 16 Sep 2012 20:00:37 +0000 (+0200) Subject: (bug 27202) Add timestamp sort to list=allimages X-Git-Tag: 1.31.0-rc.0~21445 X-Git-Url: http://git.cyclocoop.org/%24action?a=commitdiff_plain;h=529db96cdc31c5a1870ee0df623f21912a73c9aa;p=lhc%2Fweb%2Fwiklou.git (bug 27202) Add timestamp sort to list=allimages New parameter 'sort' that defaults to 'name' so if not specified it will act exactly as before. Changes in the access to $params['dir'] to a boolean variable $ascendingOrder in a try to be more meaningful. Some existing filtering parameters are mutually exclusive with new ones. The reason is because the table is sorted by name, as primary key, while sorting by timestamp use the timestamp or usertext_timestamp index which is not the primary key, thus filtering and ordering by different indices doesn't make an efficient index usage. Exceptions are: * mime: disabled in Miser Mode * size: although there's apparently no use for this when sorting by timestamp, there's an index on that and the EXPLAIN doesn't use filesort algorythm (unlike other combinations): EXPLAIN select img_name, img_user_text, img_timestamp from image where img_size>=1000 and img_size<=20000 and img_timestamp > '20120916153705' ORDER BY img_timestamp\G * Same for sha1 (see comment on Patchset 1) In function run: * Added USE INDEX query options when sort=timestamp to ensure the proper index is being used. * Table selection and result fields moved at top, then filter parameters, and finally order by and options. Parameter description and definition sorted by purpose: sort and dir first, then pagination, result fields, filters and limit. Change-Id: Ide2ff3dbc3e617e749685d558444854617535bab --- diff --git a/RELEASE-NOTES-1.21 b/RELEASE-NOTES-1.21 index 3eee8c8575..73a41b59c8 100644 --- a/RELEASE-NOTES-1.21 +++ b/RELEASE-NOTES-1.21 @@ -113,6 +113,7 @@ production. * (bug 40111) Disable minor edit for page/section creation by API. * (bug 41042) Revert change to action=parse&page=... behavior when the page does not exist. +* (bug 27202) Add timestamp sort to list=allimages. === Languages updated in 1.21 === diff --git a/includes/api/ApiQueryAllImages.php b/includes/api/ApiQueryAllImages.php index 45cc40476e..e6a0194607 100644 --- a/includes/api/ApiQueryAllImages.php +++ b/includes/api/ApiQueryAllImages.php @@ -81,30 +81,90 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { $this->dieUsage( 'Local file repository does not support querying all images', 'unsupportedrepo' ); } + $prefix = $this->getModulePrefix(); + $db = $this->getDB(); $params = $this->extractRequestParams(); - if ( !is_null( $params['continue'] ) ) { - $cont = explode( '|', $params['continue'] ); - if ( count( $cont ) != 1 ) { - $this->dieUsage( "Invalid continue param. You should pass the " . - "original value returned by the previous query", "_badcontinue" ); - } - $op = $params['dir'] == 'descending' ? '<' : '>'; - $cont_from = $db->addQuotes( $cont[0] ); - $this->addWhere( "img_name $op= $cont_from" ); + // Table and return fields + $this->addTables( 'image' ); + + $prop = array_flip( $params['prop'] ); + $this->addFields( LocalFile::selectFields() ); + + $ascendingOrder = true; + if ( $params['dir'] == 'descending' || $params['dir'] == 'older') { + $ascendingOrder = false; } - // Image filters - $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' ); - $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) ); - $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) ); - $this->addWhereRange( 'img_name', $dir, $from, $to ); + if ( $params['sort'] == 'name' ) { + // Check mutually exclusive params + $disallowed = array( 'start', 'end', 'user' ); + foreach ( $disallowed as $pname ) { + if ( isset( $params[$pname] ) ) { + $this->dieUsage( "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=timestamp", 'badparams' ); + } + } + if ( $params['filterbots'] != 'all' ) { + $this->dieUsage( "Parameter '{$prefix}filterbots' can only be used with {$prefix}sort=timestamp", 'badparams' ); + } + + // Pagination + if ( !is_null( $params['continue'] ) ) { + $cont = explode( '|', $params['continue'] ); + if ( count( $cont ) != 1 ) { + $this->dieUsage( 'Invalid continue param. You should pass the ' . + 'original value returned by the previous query', '_badcontinue' ); + } + $op = ( $ascendingOrder ? '>' : '<' ); + $continueFrom = $db->addQuotes( $cont[0] ); + $this->addWhere( "img_name $op= $continueFrom" ); + } + + // Image filters + $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) ); + $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) ); + $this->addWhereRange( 'img_name', ( $ascendingOrder ? 'newer' : 'older' ), $from, $to ); + + if ( isset( $params['prefix'] ) ) { + $this->addWhere( 'img_name' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) ); + } + } else { + // Check mutually exclusive params + $disallowed = array( 'from', 'to', 'prefix' ); + foreach ( $disallowed as $pname ) { + if ( isset( $params[$pname] ) ) { + $this->dieUsage( "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=name", 'badparams' ); + } + } + if ( !is_null( $params['user'] ) && $params['filterbots'] != 'all' ) { + // Since filterbots checks if each user has the bot right, it doesn't make sense to use it with user + $this->dieUsage( "Parameters '{$prefix}user' and '{$prefix}filterbots' cannot be used together", 'badparams' ); + } + + // Pagination + $this->addTimestampWhereRange( 'img_timestamp', ( $ascendingOrder ? 'newer' : 'older' ), $params['start'], $params['end'] ); - if ( isset( $params['prefix'] ) ) - $this->addWhere( 'img_name' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) ); + // Image filters + if ( !is_null( $params['user'] ) ) { + $this->addWhereFld( 'img_user_text', $params['user'] ); + } + if ( $params['filterbots'] != 'all' ) { + $this->addTables( 'user_groups' ); + $this->addJoinConds( array( 'user_groups' => array( + 'LEFT JOIN', + array( + 'ug_group' => User::getGroupsWithPermission( 'bot' ), + 'ug_user = img_user' + ) + ) ) ); + $groupCond = ( $params['filterbots'] == 'nobots' ? 'NULL': 'NOT NULL' ); + $this->addWhere( "ug_group IS $groupCond" ); + } + } + // Filters not depending on sort if ( isset( $params['minsize'] ) ) { $this->addWhere( 'img_size>=' . intval( $params['minsize'] ) ); } @@ -141,15 +201,22 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { $this->addWhereFld( 'img_minor_mime', $minor ); } - $this->addTables( 'image' ); - - $prop = array_flip( $params['prop'] ); - $this->addFields( LocalFile::selectFields() ); - $limit = $params['limit']; $this->addOption( 'LIMIT', $limit + 1 ); - $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' ); - $this->addOption( 'ORDER BY', 'img_name' . $sort ); + $sortFlag = ''; + if ( !$ascendingOrder ) { + $sortFlag = ' DESC'; + } + if ( $params['sort'] == 'timestamp' ) { + $this->addOption( 'ORDER BY', 'img_timestamp' . $sortFlag ); + if ( !is_null( $params['user'] ) ) { + $this->addOption( 'USE INDEX', array( 'image' => 'img_usertext_timestamp' ) ); + } else { + $this->addOption( 'USE INDEX', array( 'image' => 'img_timestamp' ) ); + } + } else { + $this->addOption( 'ORDER BY', 'img_name' . $sortFlag ); + } $res = $this->select( __METHOD__ ); @@ -159,7 +226,11 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { foreach ( $res as $row ) { if ( ++ $count > $limit ) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... - $this->setContinueEnumParameter( 'continue', $row->img_name ); + if ( $params['sort'] == 'name' ) { + $this->setContinueEnumParameter( 'continue', $row->img_name ); + } else { + $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->img_timestamp ) ); + } break; } @@ -171,7 +242,11 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $info ); if ( !$fit ) { - $this->setContinueEnumParameter( 'continue', $row->img_name ); + if ( $params['sort'] == 'name' ) { + $this->setContinueEnumParameter( 'continue', $row->img_name ); + } else { + $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->img_timestamp ) ); + } break; } } else { @@ -188,9 +263,38 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { public function getAllowedParams() { return array ( + 'sort' => array( + ApiBase::PARAM_DFLT => 'name', + ApiBase::PARAM_TYPE => array( + 'name', + 'timestamp' + ) + ), + 'dir' => array( + ApiBase::PARAM_DFLT => 'ascending', + ApiBase::PARAM_TYPE => array( + // sort=name + 'ascending', + 'descending', + // sort=timestamp + 'newer', + 'older' + ) + ), 'from' => null, - 'continue' => null, 'to' => null, + 'continue' => null, + 'start' => array( + ApiBase::PARAM_TYPE => 'timestamp' + ), + 'end' => array( + ApiBase::PARAM_TYPE => 'timestamp' + ), + 'prop' => array( + ApiBase::PARAM_TYPE => ApiQueryImageInfo::getPropertyNames( $this->propertyFilter ), + ApiBase::PARAM_DFLT => 'timestamp|url', + ApiBase::PARAM_ISMULTI => true + ), 'prefix' => null, 'minsize' => array( ApiBase::PARAM_TYPE => 'integer', @@ -198,6 +302,20 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { 'maxsize' => array( ApiBase::PARAM_TYPE => 'integer', ), + 'sha1' => null, + 'sha1base36' => null, + 'user' => array( + ApiBase::PARAM_TYPE => 'user' + ), + 'filterbots' => array( + ApiBase::PARAM_DFLT => 'all', + ApiBase::PARAM_TYPE => array( + 'all', + 'bots', + 'nobots' + ) + ), + 'mime' => null, 'limit' => array( ApiBase::PARAM_DFLT => 10, ApiBase::PARAM_TYPE => 'limit', @@ -205,38 +323,29 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 ), - 'dir' => array( - ApiBase::PARAM_DFLT => 'ascending', - ApiBase::PARAM_TYPE => array( - 'ascending', - 'descending' - ) - ), - 'sha1' => null, - 'sha1base36' => null, - 'prop' => array( - ApiBase::PARAM_TYPE => ApiQueryImageInfo::getPropertyNames( $this->propertyFilter ), - ApiBase::PARAM_DFLT => 'timestamp|url', - ApiBase::PARAM_ISMULTI => true - ), - 'mime' => null, ); } public function getParamDescription() { + $p = $this->getModulePrefix(); return array( - 'from' => 'The image title to start enumerating from', - 'continue' => 'When more results are available, use this to continue', - 'to' => 'The image title to stop enumerating at', - 'prefix' => 'Search for all image titles that begin with this value', + 'sort' => 'Property to sort by', 'dir' => 'The direction in which to list', + 'from' => "The image title to start enumerating from. Can only be used with {$p}sort=name", + 'to' => "The image title to stop enumerating at. Can only be used with {$p}sort=name", + 'continue' => 'When more results are available, use this to continue', + 'start' => "The timestamp to start enumerating from. Can only be used with {$p}sort=timestamp", + 'end' => "The timestamp to end enumerating. Can only be used with {$p}sort=timestamp", + 'prop' => ApiQueryImageInfo::getPropertyDescriptions( $this->propertyFilter ), + 'prefix' => "Search for all image titles that begin with this value. Can only be used with {$p}sort=name", 'minsize' => 'Limit to images with at least this many bytes', 'maxsize' => 'Limit to images with at most this many bytes', - 'limit' => 'How many images in total to return', - 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36", + 'sha1' => "SHA1 hash of image. Overrides {$p}sha1base36", 'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki)', - 'prop' => ApiQueryImageInfo::getPropertyDescriptions( $this->propertyFilter ), + 'user' => "Only return files uploaded by this user. Can only be used with {$p}sort=timestamp. Cannot be used together with {$p}filterbots", + 'filterbots' => "How to filter files uploaded by bots. Can only be used with {$p}sort=timestamp. Cannot be used together with {$p}user", 'mime' => 'What MIME type to search for. e.g. image/jpeg. Disabled in Miser Mode', + 'limit' => 'How many images in total to return', ); } @@ -260,8 +369,17 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { } public function getPossibleErrors() { + $p = $this->getModulePrefix(); return array_merge( parent::getPossibleErrors(), array( array( 'code' => 'params', 'info' => 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator' ), + array( 'code' => 'badparams', 'info' => "Parameter'{$p}start' can only be used with {$p}sort=timestamp" ), + array( 'code' => 'badparams', 'info' => "Parameter'{$p}end' can only be used with {$p}sort=timestamp" ), + array( 'code' => 'badparams', 'info' => "Parameter'{$p}user' can only be used with {$p}sort=timestamp" ), + array( 'code' => 'badparams', 'info' => "Parameter'{$p}filterbots' can only be used with {$p}sort=timestamp" ), + array( 'code' => 'badparams', 'info' => "Parameter'{$p}from' can only be used with {$p}sort=name" ), + array( 'code' => 'badparams', 'info' => "Parameter'{$p}to' can only be used with {$p}sort=name" ), + array( 'code' => 'badparams', 'info' => "Parameter'{$p}prefix' can only be used with {$p}sort=name" ), + array( 'code' => 'badparams', 'info' => "Parameters '{$p}user' and '{$p}filterbots' cannot be used together" ), array( 'code' => 'unsupportedrepo', 'info' => 'Local file repository does not support querying all images' ), array( 'code' => 'mimesearchdisabled', 'info' => 'MIME search disabled in Miser Mode' ), array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ), @@ -274,11 +392,15 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { return array( 'api.php?action=query&list=allimages&aifrom=B' => array( 'Simple Use', - 'Show a list of images starting at the letter "B"', + 'Show a list of files starting at the letter "B"', + ), + 'api.php?action=query&list=allimages&aiprop=user|timestamp|url&aisort=timestamp&aidir=older' => array( + 'Simple Use', + 'Show a list of recently uploaded files similar to Special:NewFiles', ), 'api.php?action=query&generator=allimages&gailimit=4&gaifrom=T&prop=imageinfo' => array( 'Using as Generator', - 'Show info about 4 images starting at the letter "T"', + 'Show info about 4 files starting at the letter "T"', ), ); }