From: Roan Kattouw Date: Thu, 5 Feb 2009 14:30:59 +0000 (+0000) Subject: * API: BREAKING CHANGE: (bug 11430) Return fewer results than the limit in some cases... X-Git-Tag: 1.31.0-rc.0~43032 X-Git-Url: http://git.cyclocoop.org/%22%2C%20generer_url_ecrire%28?a=commitdiff_plain;h=2df33ff098711a5492a051b66f0a88863182b686;p=lhc%2Fweb%2Fwiklou.git * API: BREAKING CHANGE: (bug 11430) Return fewer results than the limit in some cases to prevent running out of memory * This means queries could possibly return fewer results than the limit and still set a query-continue * Add iicontinue, rvcontinue, cicontinue, incontinue, amfrom to faciliate query-continue for these modules * Implemented by blocking additions to the ApiResult object if they would make it too large ** Important things like query-continue values and warnings are exempt from this check ** RSS feeds and exported XML are also exempted (size-checking them would be too messy) ** Result size is checked against $wgAPIMaxResultSize, which defaults to 8 MB For those who really care, per-file details follow: ApiResult.php: * Introduced ApiResult::$mSize which keeps track of the result size. * Introduced ApiResult::size() which calculates an array's size (which is the sum of the strlen()s of its elements). * ApiResult::addValue() now checks that the result size stays below $wgAPIMaxResultSize. If the item won't fit, it won't be added and addValue() will return false. Callers should check the return value and set a query-continue if it's false. * Closed the back door that is ApiResult::getData(): callers can't manipulate the data array directly anymore so they can't bypass the result size limit. * Added ApiResult::setIndexedTagName_internal() which will call setIndexedTagName() on an array already in the result. This is needed for the 'new' order of adding results, which means addValue()ing one result at a time until you hit the limit or run out, then calling this function to set the tag name. * Added ApiResult::disableSizeCheck() and enableSizeCheck() which disable and enable size checking in addValue(). This is used for stuff like query-continue elements and warnings which shouldn't count towards the result size. * Added ApiResult::unsetValue() which removes an element from the result and decreases $mSize. ApiBase.php: * Like ApiResult::getData(), ApiBase::getResultData() no longer returns a reference. * Use ApiResult::disableSizeCheck() in ApiBase::setWarning() ApiQueryBase.php: * Added ApiQueryBase::addPageSubItem(), which adds page subitems one item at a time. * addPageSubItem() and addPageSubItems() now return whether the subitem fit in the result. * Use ApiResult::disableSizeCheck() in setContinueEnumParameter() ApiMain.php: * Use ApiResult::disableSizeCheck() in ApiMain::substituteResultWithError() * Use getParameter() rather than $mRequest to obtain requestid DefaultSettings.php: * Added $wgAPIMaxResultSize, with a default value of 8 MB ApiQuery*.php: * Added results one at a time, and set a query-continue if the result is full. ApiQueryLangLinks.php and friends: * Migrated from addPageSubItems() to addPageSubItem(). This eliminates the need for $lastId. ApiQueryAllLinks.php, ApiQueryWatchlist.php, ApiQueryAllimages.php, ApiQuerySearch.php: * Renamed $data to something more appropriate ($pageids, $ids or $titles) ApiQuerySiteinfo.php: * Abuse siprop as a query-continue parameter and set it to all props that couldn't be processed. ApiQueryRandom.php: * Doesn't do continuations, because the result is supposed to be random. * Be smart enough to not run the second query if the results of the first didn't fit. ApiQueryImageInfo.php, ApiQueryRevisions.php, ApiQueryCategoryInfo.php, ApiQueryInfo.php: * Added continue parameter which basically skips the first so many items ApiQueryBacklinks.php: * Throw the result in a big array first and addValue() that one element at a time if necessary ** This is necessary because the results aren't retrieved in order * Introduced $this->pageMap to map namespace and title to page ID * Rewritten extractRowInfo() and extractRedirRowInfo() a little * Declared all private member variables explicitly ApiQueryDeletedrevs.php: * Use a pagemap just like in Backlinks * Introduce fake page IDs and keep track of them so we know where to add what ** This doesn't change the output format, because the fake page IDs start at 0 and are consecutive ApiQueryAllmessages.php: * Add amfrom to facilitate query-continue ApiQueryUsers.php: * Rewrite: put the getOtherUsersInfo() code in execute() --- diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 7012e1f093..71356828f2 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -3518,6 +3518,12 @@ $wgAPIListModules = array(); */ $wgAPIMaxDBRows = 5000; +/** + * The maximum size (in bytes) of an API result. + * Don't set this lower than $wgMaxArticleSize*1024 + */ +$wgAPIMaxResultSize = 8388608; + /** * Parser test suite files to be run by parserTests.php when no specific * filename is passed to it. diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 0922380fe1..643a74182c 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -147,7 +147,7 @@ abstract class ApiBase { /** * Get the result data array */ - public function & getResultData() { + public function getResultData() { return $this->getResult()->getData(); } @@ -156,20 +156,23 @@ abstract class ApiBase { * notice any changes in API. */ public function setWarning($warning) { - # If there is a warning already, append it to the existing one - $data =& $this->getResult()->getData(); + $data = $this->getResult()->getData(); if(isset($data['warnings'][$this->getModuleName()])) { # Don't add duplicate warnings $warn_regex = preg_quote($warning, '/'); if(preg_match("/{$warn_regex}(\\n|$)/", $data['warnings'][$this->getModuleName()]['*'])) return; - $warning = "{$data['warnings'][$this->getModuleName()]['*']}\n$warning"; - unset($data['warnings'][$this->getModuleName()]); + $oldwarning = $data['warnings'][$this->getModuleName()]['*']; + # If there is a warning already, append it to the existing one + $warning = "$oldwarning\n$warning"; + $this->getResult()->unsetValue('warnings', $this->getModuleName()); } $msg = array(); ApiResult :: setContent($msg, $warning); + $this->getResult()->disableSizeCheck(); $this->getResult()->addValue('warnings', $this->getModuleName(), $msg); + $this->getResult()->enableSizeCheck(); } /** @@ -905,4 +908,4 @@ abstract class ApiBase { public static function getBaseVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index f6c2bb2cba..fc5ac51c51 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -261,9 +261,13 @@ class ApiFormatFeedWrapper extends ApiFormatBase { public static function setResult($result, $feed, $feedItems) { // Store output in the Result data. // This way we can check during execution if any error has occured - $data = & $result->getData(); - $data['_feed'] = $feed; - $data['_feeditems'] = $feedItems; + // Disable size checking for this because we can't continue + // cleanly; size checking would cause more problems than it'd + // solve + $result->disableSizeCheck(); + $result->addValue(null, '_feed', $feed); + $result->addValue(null, '_feeditems', $feedItems); + $result->enableSizeCheck(); } /** @@ -282,8 +286,8 @@ class ApiFormatFeedWrapper extends ApiFormatBase { /** * This class expects the result data to be in a custom format set by self::setResult() - * $result['_feed'] - an instance of one of the $wgFeedClasses classes - * $result['_feeditems'] - an array of FeedItem instances + * $result['_feed'] - an instance of one of the $wgFeedClasses classes + * $result['_feeditems'] - an array of FeedItem instances */ public function execute() { $data = $this->getResultData(); @@ -304,4 +308,4 @@ class ApiFormatFeedWrapper extends ApiFormatBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 33e9cebf3e..7f3b4a73fb 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -361,9 +361,11 @@ class ApiMain extends ApiBase { } $this->getResult()->reset(); + $this->getResult()->disableSizeCheck(); // Re-add the id - if($this->mRequest->getCheck('requestid')) - $this->getResult()->addValue(null, 'requestid', $this->mRequest->getVal('requestid')); + $requestid = $this->getParameter('requestid'); + if(!is_null($requestid)) + $this->getResult()->addValue(null, 'requestid', $requestid); $this->getResult()->addValue(null, 'error', $errMessage); return $errMessage['code']; @@ -374,8 +376,9 @@ class ApiMain extends ApiBase { */ protected function executeAction() { // First add the id to the top element - if($this->mRequest->getCheck('requestid')) - $this->getResult()->addValue(null, 'requestid', $this->mRequest->getVal('requestid')); + $requestid = $this->getParameter('requestid'); + if(!is_null($requestid)) + $this->getResult()->addValue(null, 'requestid', $requestid); $params = $this->extractRequestParams(); @@ -719,4 +722,4 @@ class UsageException extends Exception { public function __toString() { return "{$this->getCodeString()}: {$this->getMessage()}"; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 0ffabc3dcd..468d350505 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -255,6 +255,10 @@ class ApiQuery extends ApiBase { $pageSet = $this->getPageSet(); $result = $this->getResult(); + # We don't check for a full result set here because we can't be adding + # more than 380K. The maximum revision size is in the megabyte range, + # and the maximum result size must be even higher than that. + // Title normalizations $normValues = array (); foreach ($pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr) { @@ -368,6 +372,10 @@ class ApiQuery extends ApiBase { $exporter->closeStream(); $exportxml = ob_get_contents(); ob_end_clean(); + // Don't check the size of exported stuff + // It's not continuable, so it would cause more + // problems than it'd solve + $result->disableSizeCheck(); if ($this->params['exportnowrap']) { $result->reset(); // Raw formatter will handle this @@ -378,6 +386,7 @@ class ApiQuery extends ApiBase { ApiResult::setContent($r, $exportxml); $result->addValue('query', 'export', $r); } + $result->enableSizeCheck(); } } } @@ -552,4 +561,4 @@ class ApiQuery extends ApiBase { $vers[] = $psModule->getVersion(); return $vers; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php index b8c06e9572..ea38742429 100644 --- a/includes/api/ApiQueryAllCategories.php +++ b/includes/api/ApiQueryAllCategories.php @@ -110,14 +110,18 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { } if( isset( $prop['hidden'] ) && $row->cat_hidden ) $item['hidden'] = ''; - $categories[] = $item; + $fit = $result->addValue(array('query', $this->getModuleName()), null, $item); + if(!$fit) + { + $this->setContinueEnumParameter('from', $this->keyToTitle($row->cat_title)); + break; + } } } $db->freeResult($res); if (is_null($resultPageSet)) { - $result->setIndexedTagName($categories, 'c'); - $result->addValue('query', $this->getModuleName(), $categories); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'c'); } else { $resultPageSet->populateFromTitles($pages); } @@ -173,4 +177,4 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php index 19d35d64d6..3238904b40 100644 --- a/includes/api/ApiQueryAllLinks.php +++ b/includes/api/ApiQueryAllLinks.php @@ -101,8 +101,9 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); - $data = array (); + $pageids = array (); $count = 0; + $result = $this->getResult(); while ($row = $db->fetchObject($res)) { if (++ $count > $limit) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... @@ -123,7 +124,13 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { $vals['ns'] = intval($title->getNamespace()); $vals['title'] = $title->getPrefixedText(); } - $data[] = $vals; + $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('continue', + $this->keyToTitle($row->pl_title) . "|" . $row->pl_from); + break; + } } else { $pageids[] = $row->pl_from; } @@ -131,9 +138,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { $db->freeResult($res); if (is_null($resultPageSet)) { - $result = $this->getResult(); - $result->setIndexedTagName($data, 'l'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'l'); } else { $resultPageSet->populateFromPageIDs($pageids); } @@ -192,4 +197,4 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php index aeb0e2d1c1..fd36b19bb3 100644 --- a/includes/api/ApiQueryAllUsers.php +++ b/includes/api/ApiQueryAllUsers.php @@ -127,7 +127,16 @@ class ApiQueryAllUsers extends ApiQueryBase { if (!$row || $lastUser !== $row->user_name) { // Save the last pass's user data if (is_array($lastUserData)) - $data[] = $lastUserData; + { + $fit = $result->addValue(array('query', $this->getModuleName()), + null, $lastUserData); + if(!$fit) + { + $this->setContinueEnumParameter('from', + $this->keyToTitle($lastUserData['name'])); + break; + } + } // No more rows left if (!$row) @@ -169,8 +178,7 @@ class ApiQueryAllUsers extends ApiQueryBase { $db->freeResult($res); - $result->setIndexedTagName($data, 'u'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'u'); } public function getAllowedParams() { @@ -226,4 +234,4 @@ class ApiQueryAllUsers extends ApiQueryBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryAllimages.php b/includes/api/ApiQueryAllimages.php index 040457e126..a4962c80e1 100644 --- a/includes/api/ApiQueryAllimages.php +++ b/includes/api/ApiQueryAllimages.php @@ -97,7 +97,7 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); - $data = array (); + $titles = array(); $count = 0; $result = $this->getResult(); while ($row = $db->fetchObject($res)) { @@ -110,20 +110,23 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { if (is_null($resultPageSet)) { $file = $repo->newFileFromRow( $row ); - $data[] = array_merge(array('name' => $row->img_name), + $info = array_merge(array('name' => $row->img_name), ApiQueryImageInfo::getInfo($file, $prop, $result)); + $fit = $result->addValue(array('query', $this->getModuleName()), null, $info); + if( !$fit ) { + $this->setContinueEnumParameter('from', $this->keyToTitle($row->img_name)); + break; + } } else { - $data[] = Title::makeTitle(NS_FILE, $row->img_name); + $titles[] = Title::makeTitle(NS_IMAGE, $row->img_name); } } $db->freeResult($res); if (is_null($resultPageSet)) { - $result = $this->getResult(); - $result->setIndexedTagName($data, 'img'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'img'); } else { - $resultPageSet->populateFromTitles( $data ); + $resultPageSet->populateFromTitles($titles); } } diff --git a/includes/api/ApiQueryAllmessages.php b/includes/api/ApiQueryAllmessages.php index 96b131b9b7..1c0b5326d1 100644 --- a/includes/api/ApiQueryAllmessages.php +++ b/includes/api/ApiQueryAllmessages.php @@ -75,6 +75,9 @@ class ApiQueryAllmessages extends ApiQueryBase { //Get all requested messages $messages = array(); foreach( $messages_target as $message ) { + if(!is_null($params['from'])) + if($message < $params['from']) + continue; $messages[$message] = wfMsg( $message ); } @@ -89,10 +92,14 @@ class ApiQueryAllmessages extends ApiQueryBase { } else { $result->setContent( $message, $value ); } - $messages_out[] = $message; + $fit = $result->addValue(array('query', $this->getModuleName()), null, $message); + if(!$fit) + { + $this->setContinueEnumParameter('from', $name); + break; + } } - $result->setIndexedTagName( $messages_out, 'message' ); - $result->addValue( 'query', $this->getModuleName(), $messages_out ); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'message'); } public function getAllowedParams() { @@ -102,6 +109,7 @@ class ApiQueryAllmessages extends ApiQueryBase { ), 'filter' => array(), 'lang' => null, + 'from' => null, ); } @@ -110,6 +118,7 @@ class ApiQueryAllmessages extends ApiQueryBase { 'messages' => 'Which messages to output. "*" means all messages', 'filter' => 'Return only messages that contain this string', 'lang' => 'Return messages in this language', + 'from' => 'Return messages starting at this message', ); } @@ -127,4 +136,4 @@ class ApiQueryAllmessages extends ApiQueryBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php index 8b221693e8..439129063d 100644 --- a/includes/api/ApiQueryAllpages.php +++ b/includes/api/ApiQueryAllpages.php @@ -135,8 +135,8 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $this->addOption('LIMIT', $limit+1); $res = $this->select(__METHOD__); - $data = array (); $count = 0; + $result = $this->getResult(); while ($row = $db->fetchObject($res)) { if (++ $count > $limit) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... @@ -147,10 +147,16 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { if (is_null($resultPageSet)) { $title = Title :: makeTitle($row->page_namespace, $row->page_title); - $data[] = array( + $vals = array( 'pageid' => intval($row->page_id), 'ns' => intval($title->getNamespace()), 'title' => $title->getPrefixedText()); + $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('from', $this->keyToTitle($row->page_title)); + break; + } } else { $resultPageSet->processDbRow($row); } @@ -158,9 +164,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $db->freeResult($res); if (is_null($resultPageSet)) { - $result = $this->getResult(); - $result->setIndexedTagName($data, 'p'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'p'); } } @@ -266,4 +270,4 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php index 0beb98a2f9..8eec410baf 100644 --- a/includes/api/ApiQueryBacklinks.php +++ b/includes/api/ApiQueryBacklinks.php @@ -38,7 +38,9 @@ if (!defined('MEDIAWIKI')) { */ class ApiQueryBacklinks extends ApiQueryGeneratorBase { - private $params, $rootTitle, $contRedirs, $contLevel, $contTitle, $contID, $redirID; + private $params, $rootTitle, $contRedirs, $contLevel, $contTitle, $contID, $redirID, $redirect; + private $bl_ns, $bl_from, $bl_table, $bl_code, $bl_title, $bl_sort, $bl_fields, $hasNS; + private $pageMap, $resultArr; // output element name, database column field prefix, database table private $backlinksSettings = array ( @@ -61,6 +63,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { public function __construct($query, $moduleName) { extract($this->backlinksSettings[$moduleName]); + $this->resultArr = array(); parent :: __construct($query, $moduleName, $code); $this->bl_ns = $prefix . '_namespace'; @@ -188,7 +191,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__.'::firstQuery'); $count = 0; - $this->data = array (); + $this->pageMap = array(); // Maps ns and title to pageid $this->continueStr = null; $this->redirTitles = array(); while ($row = $db->fetchObject($res)) { @@ -223,10 +226,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { // We've reached the one extra which shows that there are additional pages to be had. Stop here... // We need to keep the parent page of this redir in if($this->hasNS) - $contTitle = Title::makeTitle($row->{$this->bl_ns}, $row->{$this->bl_title}); + $parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}]; else - $contTitle = Title::makeTitle(NS_FILE, $row->{$this->bl_title}); - $this->continueStr = $this->getContinueRedirStr($contTitle->getArticleID(), $row->page_id); + $parentID = $this->pageMap[NS_IMAGE][$row->{$this->bl_title}]; + $this->continueStr = $this->getContinueRedirStr($parentID, $row->page_id); break; } @@ -237,30 +240,67 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } $db->freeResult($res); } + // Try to add the result data in one go and pray that it fits + $fit = $this->getResult()->addValue('query', $this->getModuleName(), $this->resultArr); + if(!$fit) + { + // It didn't fit. Add elements one by one until the + // result is full. + foreach($this->resultArr as $pageID => $arr) + { + // Add the basic entry without redirlinks first + $fit = $this->getResult()->addValue( + array('query', $this->getModuleName()), + $pageID, array_diff_key($arr, array('redirlinks' => ''))); + if(!$fit) + { + $this->continueStr = $this->getContinueStr($pageID); + break; + } + + $hasRedirs = false; + foreach((array)@$arr['redirlinks'] as $key => $redir) + { + $fit = $this->getResult()->addValue( + array('query', $this->getModuleName(), $pageID, 'redirlinks'), + $key, $redir); + if(!$fit) + { + $this->continueStr = $this->getContinueRedirStr($pageID, $redir['pageid']); + break; + } + $hasRedirs = true; + } + if($hasRedirs) + $this->getResult()->setIndexedTagName_internal( + array('query', $this->getModuleName(), $pageID, 'redirlinks'), + $this->bl_code); + if(!$fit) + break; + } + } if(!is_null($this->continueStr)) $this->setContinueEnumParameter('continue', $this->continueStr); if (is_null($resultPageSet)) { - $resultData = array(); - foreach($this->data as $ns => $a) - foreach($a as $title => $arr) - $resultData[] = $arr; - $result = $this->getResult(); - $result->setIndexedTagName($resultData, $this->bl_code); - $result->addValue('query', $this->getModuleName(), $resultData); + $this->getResult()->setIndexedTagName_internal( + array('query', $this->getModuleName()), + $this->bl_code); } } private function extractRowInfo($row) { - if(!isset($this->data[$row->page_namespace][$row->page_title])) { - $this->data[$row->page_namespace][$row->page_title]['pageid'] = $row->page_id; - ApiQueryBase::addTitleInfo($this->data[$row->page_namespace][$row->page_title], Title::makeTitle($row->page_namespace, $row->page_title)); - if($row->page_is_redirect) - { - $this->data[$row->page_namespace][$row->page_title]['redirect'] = ''; - $this->redirTitles[] = Title::makeTitle($row->page_namespace, $row->page_title); - } + $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id; + $t = Title::makeTitle($row->page_namespace, $row->page_title); + $a = array('pageid' => $row->page_id); + ApiQueryBase::addTitleInfo($a, $t); + if($row->page_is_redirect) + { + $a['redirect'] = ''; + $this->redirTitles[] = $t; } + // Put all the results in an array first + $this->resultArr[$a['pageid']] = $a; } private function extractRedirRowInfo($row) @@ -270,8 +310,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { if($row->page_is_redirect) $a['redirect'] = ''; $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE; - $this->data[$ns][$row->{$this->bl_title}]['redirlinks'][] = $a; - $this->getResult()->setIndexedTagName($this->data[$ns][$row->{$this->bl_title}]['redirlinks'], $this->bl_code); + $parentID = $this->pageMap[$ns][$row->{$this->bl_title}]; + // Put all the results in an array first + $this->resultArr[$parentID]['redirlinks'][] = $a; + $this->getResult()->setIndexedTagName($this->resultArr[$parentID]['redirlinks'], $this->bl_code); } protected function processContinue() { diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index c8951a0097..73f70ebecb 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -272,15 +272,37 @@ abstract class ApiQueryBase extends ApiBase { /** * Add a sub-element under the page element with the given page ID * @param int $pageId Page ID - * @param array $data Data array à la ApiResult + * @param array $data Data array à la ApiResult + * @return bool Whether the element fit in the result */ protected function addPageSubItems($pageId, $data) { $result = $this->getResult(); $result->setIndexedTagName($data, $this->getModulePrefix()); - $result->addValue(array ('query', 'pages', intval($pageId)), + return $result->addValue(array('query', 'pages', intval($pageId)), $this->getModuleName(), $data); } + + /** + * Same as addPageSubItems(), but one element of $data + * at a time + * @param int $pageId Page ID + * @param array $data Data array à la ApiResult + * @param string $elemname XML element name. If null, getModuleName() is used + * @return bool Whether the element fit in the result + */ + protected function addPageSubItem($pageId, $item, $elemname = null) { + if(is_null($elemname)) + $elemname = $this->getModulePrefix(); + $result = $this->getResult(); + $fit = $result->addValue(array('query', 'pages', $pageId, + $this->getModuleName()), null, $item); + if(!$fit) + return false; + $result->setIndexedTagName_internal(array('query', 'pages', $pageId, + $this->getModuleName()), $elemname); + return true; + } /** * Set a query-continue value @@ -288,10 +310,11 @@ abstract class ApiQueryBase extends ApiBase { * @param $paramValue Parameter value */ protected function setContinueEnumParameter($paramName, $paramValue) { - $paramName = $this->encodeParamName($paramName); $msg = array( $paramName => $paramValue ); + $this->getResult()->disableSizeCheck(); $this->getResult()->addValue('query-continue', $this->getModuleName(), $msg); + $this->getResult()->enableSizeCheck(); } /** diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php index efee5b243f..a2a77a1442 100644 --- a/includes/api/ApiQueryBlocks.php +++ b/includes/api/ApiQueryBlocks.php @@ -169,10 +169,14 @@ class ApiQueryBlocks extends ApiQueryBase { if($row->ipb_allow_usertalk) $block['allowusertalk'] = ''; } - $data[] = $block; + $fit = $result->addValue(array('query', $this->getModuleName()), null, $block); + if(!$fit) + { + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ipb_timestamp)); + break; + } } - $result->setIndexedTagName($data, 'block'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'block'); } protected function prepareUsername($user) @@ -261,4 +265,4 @@ class ApiQueryBlocks extends ApiQueryBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php index badca7ccc7..d6f61772ee 100644 --- a/includes/api/ApiQueryCategories.php +++ b/includes/api/ApiQueryCategories.php @@ -137,8 +137,6 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { if (is_null($resultPageSet)) { - $data = array(); - $lastId = 0; // database has no ID 0 $count = 0; while ($row = $db->fetchObject($res)) { if (++$count > $params['limit']) { @@ -148,16 +146,8 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { '|' . $this->keyToTitle($row->cl_to)); break; } - if ($lastId != $row->cl_from) { - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); - $data = array(); - } - $lastId = $row->cl_from; - } $title = Title :: makeTitle(NS_CATEGORY, $row->cl_to); - $vals = array(); ApiQueryBase :: addTitleInfo($vals, $title); if ($fld_sortkey) @@ -165,13 +155,14 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { if ($fld_timestamp) $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->cl_timestamp); - $data[] = $vals; - } - - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); + $fit = $this->addPageSubItem($row->cl_from, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('continue', $row->cl_from . + '|' . $this->keyToTitle($row->cl_to)); + break; + } } - } else { $titles = array(); @@ -248,4 +239,4 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php index 1b578b5b49..115bd61315 100644 --- a/includes/api/ApiQueryCategoryInfo.php +++ b/includes/api/ApiQueryCategoryInfo.php @@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) { } /** - * This query adds subelement to all pages with the list of images embedded into those pages. + * This query adds the subelement to all pages with the list of categories the page is in * * @ingroup API */ @@ -39,7 +39,8 @@ class ApiQueryCategoryInfo extends ApiQueryBase { parent :: __construct($query, $moduleName, 'ci'); } - public function execute() { + public function execute() { + $params = $this->extractRequestParams(); $alltitles = $this->getPageSet()->getAllTitlesByNamespace(); if ( empty( $alltitles[NS_CATEGORY] ) ) { return; @@ -65,13 +66,20 @@ class ApiQueryCategoryInfo extends ApiQueryBase { 'pp_propname' => 'hiddencat')), )); $this->addFields(array('cat_title', 'cat_pages', 'cat_subcats', 'cat_files', 'pp_propname AS cat_hidden')); - $this->addWhere(array('cat_title' => $cattitles)); + $this->addWhere(array('cat_title' => $cattitles)); + if(!is_null($params['continue'])) + { + // We need to set a LIMIT in order to be able to set + // an OFFSET + $this->addOption('LIMIT', count($titles)); + $this->addOption('OFFSET', $params['continue']); + } $db = $this->getDB(); $res = $this->select(__METHOD__); - $data = array(); $catids = array_flip($cattitles); + $count = (int)@$params['continue']; while($row = $db->fetchObject($res)) { $vals = array(); @@ -81,11 +89,29 @@ class ApiQueryCategoryInfo extends ApiQueryBase { $vals['subcats'] = $row->cat_subcats; if($row->cat_hidden) $vals['hidden'] = ''; - $this->addPageSubItems($catids[$row->cat_title], $vals); + $fit = $this->addPageSubItems($catids[$row->cat_title], $vals); + if(!$fit) + { + $this->setContinueEnumParameter('continue', $count); + break; + } + $count++; } $db->freeResult($res); } + public function getAllowedParams() { + return array ( + 'continue' => null, + ); + } + + public function getParamDescription() { + return array ( + 'continue' => 'When more results are available, use this to continue', + ); + } + public function getDescription() { return 'Returns information about the given categories'; } @@ -97,4 +123,4 @@ class ApiQueryCategoryInfo extends ApiQueryBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php index ba8a472815..6e88012c7a 100644 --- a/includes/api/ApiQueryCategoryMembers.php +++ b/includes/api/ApiQueryCategoryMembers.php @@ -112,8 +112,6 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { break; } - $lastSortKey = $row->cl_sortkey; // detect duplicate sortkeys - if (is_null($resultPageSet)) { $vals = array(); if ($fld_ids) @@ -127,16 +125,26 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { $vals['sortkey'] = $row->cl_sortkey; if ($fld_timestamp) $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->cl_timestamp); - $data[] = $vals; + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), + null, $vals); + if(!$fit) + { + if ($params['sort'] == 'timestamp') + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->cl_timestamp)); + else + $this->setContinueEnumParameter('continue', $this->getContinueStr($row, $lastSortKey)); + break; + } } else { $resultPageSet->processDbRow($row); } + $lastSortKey = $row->cl_sortkey; // detect duplicate sortkeys } $db->freeResult($res); if (is_null($resultPageSet)) { - $this->getResult()->setIndexedTagName($data, 'cm'); - $this->getResult()->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal( + array('query', $this->getModuleName()), 'cm'); } } @@ -257,4 +265,4 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php index 36490eb5bc..8ef0e8eede 100644 --- a/includes/api/ApiQueryDeletedrevs.php +++ b/includes/api/ApiQueryDeletedrevs.php @@ -173,9 +173,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $this->addWhereRange('ar_timestamp', $params['dir'], $params['start'], $params['end']); } $res = $this->select(__METHOD__); - $pages = array(); + $pageMap = array(); // Maps ns&title to (fake) pageid $count = 0; - // First populate the $pages array + $newPageID = 0; while($row = $db->fetchObject($res)) { if(++$count > $limit) @@ -205,31 +205,37 @@ class ApiQueryDeletedrevs extends ApiQueryBase { if($fld_content) ApiResult::setContent($rev, Revision::getRevisionText($row)); - $t = Title::makeTitle($row->ar_namespace, $row->ar_title); - if(!isset($pages[$t->getPrefixedText()])) + if(!isset($pageMap[$row->ar_namespace][$row->ar_title])) { - $pages[$t->getPrefixedText()] = array( + $pageID = $newPageID++; + $pageMap[$row->ar_namespace][$row->ar_title] = $pageID; + $t = Title::makeTitle($row->ar_namespace, $row->ar_title); + $a = array( 'title' => $t->getPrefixedText(), 'ns' => intval($row->ar_namespace), 'revisions' => array($rev) ); + $result->setIndexedTagName($a['revisions'], 'rev'); if($fld_token) - $pages[$t->getPrefixedText()]['token'] = $token; + $a['token'] = $token; + $fit = $result->addValue(array('query', $this->getModuleName()), $pageID, $a); } else - $pages[$t->getPrefixedText()]['revisions'][] = $rev; + { + $pageID = $pageMap[$row->ar_namespace][$row->ar_title]; + $fit = $result->addValue( + array('query', $this->getModuleName(), $pageID, 'revisions'), + null, $rev); + } + if(!$fit) + { + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp)); + break; + } } $db->freeResult($res); - - // We don't want entire pagenames as keys, so let's make this array indexed - foreach($pages as $page) - { - $result->setIndexedTagName($page['revisions'], 'rev'); - $data[] = $page; - } - $result->setIndexedTagName($data, 'page'); - $result->addValue('query', $this->getModuleName(), $data); - } + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'page'); + } public function getAllowedParams() { return array ( @@ -325,4 +331,4 @@ class ApiQueryDeletedrevs extends ApiQueryBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php index f7171b8948..67bae8bb4f 100644 --- a/includes/api/ApiQueryDuplicateFiles.php +++ b/includes/api/ApiQueryDuplicateFiles.php @@ -86,9 +86,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); $db = $this->getDB(); $count = 0; - $data = array(); $titles = array(); - $lastName = ''; while($row = $db->fetchObject($res)) { if(++$count > $params['limit']) @@ -104,27 +102,23 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { $titles[] = Title::makeTitle(NS_FILE, $row->dup_name); else { - if($row->orig_name != $lastName) - { - if($lastName != '') - { - $this->addPageSubItems($images[$lastName], $data); - $data = array(); - } - $lastName = $row->orig_name; - } - - $data[] = array( + $r = array( 'name' => $row->dup_name, 'user' => $row->dup_user_text, 'timestamp' => wfTimestamp(TS_ISO_8601, $row->dup_timestamp) ); + $fit = $this->addPageSubItem($images[$row->orig_name], $r); + if(!$fit) + { + $this->setContinueEnumParameter('continue', + $this->keyToTitle($row->orig_name) . '|' . + $this->keyToTitle($row->dup_name)); + break; + } } } if(!is_null($resultPageSet)) $resultPageSet->populateFromTitles($titles); - else if($lastName != '') - $this->addPageSubItems($images[$lastName], $data); $db->freeResult($res); } diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php index c61e53a06b..d4dcb83582 100644 --- a/includes/api/ApiQueryExtLinksUsage.php +++ b/includes/api/ApiQueryExtLinksUsage.php @@ -110,7 +110,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); - $data = array (); + $result = $this->getResult(); $count = 0; while ($row = $db->fetchObject($res)) { if (++ $count > $limit) { @@ -130,7 +130,12 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { } if ($fld_url) $vals['url'] = $row->el_to; - $data[] = $vals; + $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('offset', $offset + $count - 1); + break; + } } else { $resultPageSet->processDbRow($row); } @@ -138,9 +143,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { $db->freeResult($res); if (is_null($resultPageSet)) { - $result = $this->getResult(); - $result->setIndexedTagName($data, $this->getModulePrefix()); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), + $this->getModulePrefix()); } } @@ -208,4 +212,4 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php index 0757405540..b8591e0c87 100644 --- a/includes/api/ApiQueryExternalLinks.php +++ b/includes/api/ApiQueryExternalLinks.php @@ -61,8 +61,6 @@ class ApiQueryExternalLinks extends ApiQueryBase { $db = $this->getDB(); $res = $this->select(__METHOD__); - $data = array(); - $lastId = 0; // database has no ID 0 $count = 0; while ($row = $db->fetchObject($res)) { if (++$count > $params['limit']) { @@ -71,23 +69,15 @@ class ApiQueryExternalLinks extends ApiQueryBase { $this->setContinueEnumParameter('offset', @$params['offset'] + $params['limit']); break; } - if ($lastId != $row->el_from) { - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); - $data = array(); - } - $lastId = $row->el_from; - } - $entry = array(); ApiResult :: setContent($entry, $row->el_to); - $data[] = $entry; - } - - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); + $fit = $this->addPageSubItem($row->el_from, $entry); + if(!$fit) + { + $this->setContinueEnumParameter('offset', @$params['offset'] + $count - 1); + break; + } } - $db->freeResult($res); } @@ -125,4 +115,4 @@ class ApiQueryExternalLinks extends ApiQueryBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php index 03edc355cf..e6910017fb 100644 --- a/includes/api/ApiQueryImageInfo.php +++ b/includes/api/ApiQueryImageInfo.php @@ -56,49 +56,97 @@ class ApiQueryImageInfo extends ApiQueryBase { } $pageIds = $this->getPageSet()->getAllTitlesByNamespace(); + $cnt = 0; if (!empty($pageIds[NS_FILE])) { - $result = $this->getResult(); $images = RepoGroup::singleton()->findFiles( array_keys( $pageIds[NS_FILE] ) ); foreach ( $images as $img ) { - $data = array(); - + $cnt++; + if(!is_null($params['continue'])) + if($cnt < $params['continue']) + continue; + $pageId = $pageIds[NS_IMAGE][ $img->getOriginalTitle()->getDBkey() ]; + + $fit = $result->addValue( + array('query', 'pages', intval($pageId)), + 'imagerepository', $img->getRepoName() + ); + if(!$fit) + { + if(count($pageIds[NS_IMAGE]) == 1) + # The user is screwed. imageinfo can't be solely + # responsible for exceeding the limit in this case, + # so set a query-continue that just returns the same + # thing again. When the violating queries have been + # out-continued, the result will get through + $this->setContinueEnumParameter('start', + wfTimestamp(TS_ISO_8601, $img->getTimestamp())); + else + $this->setContinueEnumParameter('continue', $cnt); + break; + } + // Get information about the current version first // Check that the current version is within the start-end boundaries + $gotOne = false; if((is_null($params['start']) || $img->getTimestamp() <= $params['start']) && (is_null($params['end']) || $img->getTimestamp() >= $params['end'])) { - $data[] = self::getInfo( $img, $prop, $result, $scale ); + $gotOne = true; + $fit = $this->addPageSubItem($pageId, + self::getInfo( $img, $prop, $result, $scale)); + if(!$fit) + { + if(count($pageIds[NS_IMAGE]) == 1) + # See the 'the user is screwed' comment above + $this->setContinueEnumParameter('start', + wfTimestamp(TS_ISO_8601, $img->getTimestamp())); + else + $this->setContinueEnumParameter('continue', $cnt); + break; + } } // Now get the old revisions // Get one more to facilitate query-continue functionality - $count = count($data); + $count = ($gotOne ? 1 : 0); $oldies = $img->getHistory($params['limit'] - $count + 1, $params['start'], $params['end']); foreach($oldies as $oldie) { if(++$count > $params['limit']) { // We've reached the extra one which shows that there are additional pages to be had. Stop here... // Only set a query-continue if there was only one title if(count($pageIds[NS_FILE]) == 1) - $this->setContinueEnumParameter('start', $oldie->getTimestamp()); + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $oldie->getTimestamp())); + break; + } + $fit = $this->addPageSubItem($pageId, + self::getInfo($oldie, $prop, $result)); + if(!$fit) + { + if(count($pageIds[NS_IMAGE]) == 1) + $this->setContinueEnumParameter('start', + wfTimestamp(TS_ISO_8601, $oldie->getTimestamp())); + else + $this->setContinueEnumParameter('continue', $cnt); break; } - $data[] = self::getInfo( $oldie, $prop, $result ); } - - $pageId = $pageIds[NS_FILE][ $img->getOriginalTitle()->getDBkey() ]; - $result->addValue( - array( 'query', 'pages', intval( $pageId ) ), - 'imagerepository', $img->getRepoName() - ); - $this->addPageSubItems($pageId, $data); + if(!$fit) + break; } $missing = array_diff( array_keys( $pageIds[NS_FILE] ), array_keys( $images ) ); - foreach ( $missing as $title ) - $result->addValue( - array( 'query', 'pages', intval( $pageIds[NS_FILE][$title] ) ), + foreach ($missing as $title) { + $cnt++; + if(!is_null($params['continue'])) + if($count < $params['continue']) + continue; + $fit = $result->addValue( + array('query', 'pages', intval($pageIds[NS_FILE][$title])), 'imagerepository', '' ); + if(!$fit) + $this->setContinueEnumParameter('continue', $cnt); + } } } @@ -208,7 +256,8 @@ class ApiQueryImageInfo extends ApiQueryBase { 'urlheight' => array( ApiBase :: PARAM_TYPE => 'integer', ApiBase :: PARAM_DFLT => -1 - ) + ), + 'continue' => null, ); } @@ -221,6 +270,7 @@ class ApiQueryImageInfo extends ApiQueryBase { 'urlwidth' => array('If iiprop=url is set, a URL to an image scaled to this width will be returned.', 'Only the current version of the image can be scaled.'), 'urlheight' => 'Similar to iiurlwidth. Cannot be used without iiurlwidth', + 'continue' => 'When more results are available, use this to continue', ); } diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php index 4bee288b93..cc182769ba 100644 --- a/includes/api/ApiQueryImages.php +++ b/includes/api/ApiQueryImages.php @@ -82,9 +82,6 @@ class ApiQueryImages extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); if (is_null($resultPageSet)) { - - $data = array(); - $lastId = 0; // database has no ID 0 $count = 0; while ($row = $db->fetchObject($res)) { if (++$count > $params['limit']) { @@ -94,23 +91,16 @@ class ApiQueryImages extends ApiQueryGeneratorBase { '|' . $this->keyToTitle($row->il_to)); break; } - if ($lastId != $row->il_from) { - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); - $data = array(); - } - $lastId = $row->il_from; - } - $vals = array(); ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle(NS_FILE, $row->il_to)); - $data[] = $vals; - } - - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); + $fit = $this->addPageSubItem($row->il_from, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('continue', $row->il_from . + '|' . $this->keyToTitle($row->il_to)); + break; + } } - } else { $titles = array(); @@ -167,4 +157,4 @@ class ApiQueryImages extends ApiQueryGeneratorBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index 92d088c336..202cc15a11 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -434,7 +434,12 @@ class ApiQueryInfo extends ApiQueryBase { } } + $count = 0; foreach ( $titles as $pageid => $title ) { + $count++; + if(!is_null($params['continue'])) + if($count < $params['continue']) + continue; $pageInfo = array ( 'touched' => wfTimestamp(TS_ISO_8601, $pageTouched[$pageid]), 'lastrevid' => intval($pageLatest[$pageid]), @@ -480,18 +485,27 @@ class ApiQueryInfo extends ApiQueryBase { if($title->userCanRead()) $pageInfo['readable'] = ''; - $result->addValue(array ( + $fit = $result->addValue(array ( 'query', 'pages' ), $pageid, $pageInfo); + if(!$fit) + { + $this->setContinueEnumParameter('continue', $count); + break; + } } // Get properties for missing titles if requested if(!is_null($params['token']) || $fld_protection || $fld_talkid || $fld_subjectid || $fld_url || $fld_readable) { - $res = &$result->getData(); foreach($missing as $pageid => $title) { + $count++; + if(!is_null($params['continue'])) + if($count < $params['continue']) + continue; + $fit = true; if(!is_null($params['token'])) { $tokenFunctions = $this->getTokenFunctions(); @@ -502,30 +516,45 @@ class ApiQueryInfo extends ApiQueryBase { if($val === false) $this->setWarning("Action '$t' is not allowed for the current user"); else - $res['query']['pages'][$pageid][$t . 'token'] = $val; + $fit = $result->addValue( + array('query', 'pages', $pageid), + $t . 'token', $val); } } - if($fld_protection) + if($fld_protection && $fit) { // Apparently the XML formatting code doesn't like array(null) // This is painful to fix, so we'll just work around it if(isset($prottitles[$title->getNamespace()][$title->getDBkey()])) - $res['query']['pages'][$pageid]['protection'] = $prottitles[$title->getNamespace()][$title->getDBkey()]; + $val = $prottitles[$title->getNamespace()][$title->getDBkey()]; else - $res['query']['pages'][$pageid]['protection'] = array(); - $result->setIndexedTagName($res['query']['pages'][$pageid]['protection'], 'pr'); + $val = array(); + $result->setIndexedTagName($val, 'pr'); + $fit = $result->addValue( + array('query', 'pages', $pageid), + 'protection', $val); } - if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDBKey()])) - $res['query']['pages'][$pageid]['talkid'] = $talkids[$title->getNamespace()][$title->getDBKey()]; - if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDBKey()])) - $res['query']['pages'][$pageid]['subjectid'] = $subjectids[$title->getNamespace()][$title->getDBKey()]; - if($fld_url) { - $res['query']['pages'][$pageid]['fullurl'] = $title->getFullURL(); - $res['query']['pages'][$pageid]['editurl'] = $title->getFullURL('action=edit'); + if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDbKey()]) && $fit) + $fit = $result->addValue(array('query', 'pages', $pageid), 'talkid', + $talkids[$title->getNamespace()][$title->getDbKey()]); + if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDbKey()]) && $fit) + $fit = $result->addValue(array('query', 'pages', $pageid), 'subjectid', + $subjectids[$title->getNamespace()][$title->getDbKey()]); + if($fld_url && $fit) { + $fit = $result->addValue(array('query', 'pages', $pageid), 'fullurl', + $title->getFullURL()); + if($fit) + $fit = $result->addValue(array('query', 'pages', $pageid), 'editurl', + $title->getFullURL('action=edit')); } - if($fld_readable) + if($fld_readable && $fit) if($title->userCanRead()) - $res['query']['pages'][$pageid]['readable'] = ''; + $fit = $result->addValue(array('query', 'pages', $pageid), 'readable', ''); + if(!$fit) + { + $this->setContinueEnumParameter('continue', $count); + break; + } } } } @@ -546,7 +575,8 @@ class ApiQueryInfo extends ApiQueryBase { ApiBase :: PARAM_DFLT => NULL, ApiBase :: PARAM_ISMULTI => true, ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()) - ) + ), + 'continue' => null, ); } @@ -559,6 +589,7 @@ class ApiQueryInfo extends ApiQueryBase { ' subjectid - The page ID of the parent page for each talk page' ), 'token' => 'Request a token to perform a data-modifying action on a page', + 'continue' => 'When more results are available, use this to continue', ); } diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php index 9c905e3705..7aa9783490 100644 --- a/includes/api/ApiQueryLangLinks.php +++ b/includes/api/ApiQueryLangLinks.php @@ -71,8 +71,6 @@ class ApiQueryLangLinks extends ApiQueryBase { $this->addOption('LIMIT', $params['limit'] + 1); $res = $this->select(__METHOD__); - $data = array(); - $lastId = 0; // database has no ID 0 $count = 0; $db = $this->getDB(); while ($row = $db->fetchObject($res)) { @@ -82,23 +80,15 @@ class ApiQueryLangLinks extends ApiQueryBase { $this->setContinueEnumParameter('continue', "{$row->ll_from}|{$row->ll_lang}"); break; } - if ($lastId != $row->ll_from) { - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); - $data = array(); - } - $lastId = $row->ll_from; - } - $entry = array('lang' => $row->ll_lang); ApiResult :: setContent($entry, $row->ll_title); - $data[] = $entry; - } - - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); + $fit = $this->addPageSubItem($row->ll_from, $entry); + if(!$fit) + { + $this->setContinueEnumParameter('continue', "{$row->ll_from}|{$row->ll_lang}"); + break; + } } - $db->freeResult($res); } @@ -136,4 +126,4 @@ class ApiQueryLangLinks extends ApiQueryBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php index 4438c06e80..4cde3cfb03 100644 --- a/includes/api/ApiQueryLinks.php +++ b/includes/api/ApiQueryLinks.php @@ -119,9 +119,6 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); if (is_null($resultPageSet)) { - - $data = array(); - $lastId = 0; // database has no ID 0 $count = 0; while ($row = $db->fetchObject($res)) { if(++$count > $params['limit']) { @@ -132,23 +129,17 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { $this->keyToTitle($row->pl_title)); break; } - if ($lastId != $row->pl_from) { - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); - $data = array(); - } - $lastId = $row->pl_from; - } - $vals = array(); ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle($row->pl_namespace, $row->pl_title)); - $data[] = $vals; - } - - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); + $fit = $this->addPageSubItem($row->pl_from, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('continue', + "{$row->pl_from}|{$row->pl_namespace}|" . + $this->keyToTitle($row->pl_title)); + break; + } } - } else { $titles = array(); @@ -215,4 +206,4 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php index f745894e1e..6e2975276f 100644 --- a/includes/api/ApiQueryLogEvents.php +++ b/includes/api/ApiQueryLogEvents.php @@ -125,7 +125,6 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->addWhere('log_deleted & ' . LogPage::DELETED_USER . ' = 0'); } - $data = array (); $count = 0; $res = $this->select(__METHOD__); while ($row = $db->fetchObject($res)) { @@ -136,13 +135,18 @@ class ApiQueryLogEvents extends ApiQueryBase { } $vals = $this->extractRowInfo($row); - if($vals) - $data[] = $vals; + if(!$vals) + continue; + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->log_timestamp)); + break; + } } $db->freeResult($res); - $this->getResult()->setIndexedTagName($data, 'item'); - $this->getResult()->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'item'); } public static function addLogParams($result, &$vals, $params, $type, $ts) { diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php index e7b8bf46f7..a8fc147626 100644 --- a/includes/api/ApiQueryRandom.php +++ b/includes/api/ApiQueryRandom.php @@ -62,7 +62,7 @@ if (!defined('MEDIAWIKI')) { $this->addFields($resultPageSet->getPageTableFields()); } - protected function runQuery(&$data, &$resultPageSet) { + protected function runQuery(&$resultPageSet) { $db = $this->getDB(); $res = $this->select(__METHOD__); $count = 0; @@ -73,7 +73,14 @@ if (!defined('MEDIAWIKI')) { // Prevent duplicates if(!in_array($row->page_id, $this->pageIDs)) { - $data[] = $this->extractRowInfo($row); + $fit = $this->getResult()->addValue( + array('query', $this->getModuleName()), + null, $this->extractRowInfo($row)); + if(!$fit) + # We can't really query-continue a random list. + # Return an insanely high value so + # $count < $limit is false + return 1E9; $this->pageIDs[] = $row->page_id; } } @@ -87,11 +94,10 @@ if (!defined('MEDIAWIKI')) { public function run($resultPageSet = null) { $params = $this->extractRequestParams(); $result = $this->getResult(); - $data = array(); $this->pageIDs = array(); $this->prepareQuery(wfRandom(), $params['limit'], $params['namespace'], $resultPageSet, $params['redirect']); - $count = $this->runQuery($data, $resultPageSet); + $count = $this->runQuery($resultPageSet); if($count < $params['limit']) { /* We got too few pages, we probably picked a high value @@ -99,12 +105,11 @@ if (!defined('MEDIAWIKI')) { * also the comment in Title::getRandomTitle() */ $this->prepareQuery(0, $params['limit'] - $count, $params['namespace'], $resultPageSet, $params['redirect']); - $this->runQuery($data, $resultPageSet); + $this->runQuery($resultPageSet); } if(is_null($resultPageSet)) { - $result->setIndexedTagName($data, 'page'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'page'); } } @@ -157,4 +162,4 @@ if (!defined('MEDIAWIKI')) { public function getVersion() { return __CLASS__ . ': $Id: ApiQueryRandom.php overlordq$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index 10436c47c7..c900989f77 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -191,9 +191,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->token = $params['token']; $this->addOption('LIMIT', $params['limit'] +1); - $data = array (); $count = 0; - /* Perform the actual query. */ $db = $this->getDB(); $res = $this->select(__METHOD__); @@ -210,16 +208,20 @@ class ApiQueryRecentChanges extends ApiQueryBase { $vals = $this->extractRowInfo($row); /* Add that row's data to our final output. */ - if($vals) - $data[] = $vals; + if(!$vals) + continue; + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rc_timestamp)); + break; + } } $db->freeResult($res); /* Format the result */ - $result = $this->getResult(); - $result->setIndexedTagName($data, 'rc'); - $result->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'rc'); } /** @@ -453,4 +455,4 @@ class ApiQueryRecentChanges extends ApiQueryBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index 258f460f34..c88d3f998d 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -231,6 +231,8 @@ class ApiQueryRevisions extends ApiQueryBase { ApiBase :: dieDebug(__METHOD__, 'param validation?'); $this->addOption('LIMIT', $limit +1); + if(!is_null($continue)) + $this->addOption('OFFSET', $continue); $data = array (); $count = 0; @@ -246,29 +248,19 @@ class ApiQueryRevisions extends ApiQueryBase { $this->setContinueEnumParameter('startid', intval($row->rev_id)); break; } - $revision = new Revision( $row ); - $this->getResult()->addValue( - array ( - 'query', - 'pages', - $revision->getPage(), - 'revisions'), - null, - $this->extractRowInfo( $revision )); - } - $db->freeResult($res); - - // Ensure that all revisions are shown as '' elements - $result = $this->getResult(); - if ($result->getIsRawMode()) { - $data =& $result->getData(); - foreach ($data['query']['pages'] as & $page) { - if (is_array($page) && array_key_exists('revisions', $page)) { - $result->setIndexedTagName($page['revisions'], 'rev'); - } + // + $fit = $this->addPageSubItem($revision->getPage(), $this->extractRowInfo($revision), 'rev'); + if(!$fit) + { + if($enumRevMode) + $this->setContinueEnumParameter('startid', intval($row->rev_id)); + else + $this->setContinueEnumParameter('continue', $continue + $count - 1); + break; } } + $db->freeResult($res); } private function extractRowInfo( $revision ) { @@ -412,6 +404,7 @@ class ApiQueryRevisions extends ApiQueryBase { ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()), ApiBase :: PARAM_ISMULTI => true ), + 'continue' => null, ); } @@ -430,6 +423,7 @@ class ApiQueryRevisions extends ApiQueryBase { 'generatexml' => 'generate XML parse tree for revision content', 'section' => 'only retrieve the content of this section', 'token' => 'Which tokens to obtain for each revision', + 'continue' => 'When more results are available, use this to continue', ); } diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php index f79e3a098e..a34c2f68b7 100644 --- a/includes/api/ApiQuerySearch.php +++ b/includes/api/ApiQuerySearch.php @@ -87,7 +87,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { $this->dieUsage("{$what} search is disabled", "search-{$what}-disabled"); - $data = array (); + $titles = array (); $count = 0; while( $result = $matches->next() ) { if (++ $count > $limit) { @@ -102,20 +102,24 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { $title = $result->getTitle(); if (is_null($resultPageSet)) { - $data[] = array( + $vals = array( 'ns' => intval($title->getNamespace()), 'title' => $title->getPrefixedText()); + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('offset', $params['offset'] + $count - 1); + break; + } } else { - $data[] = $title; + $titles[] = $title; } } if (is_null($resultPageSet)) { - $result = $this->getResult(); - $result->setIndexedTagName($data, 'p'); - $result->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'p'); } else { - $resultPageSet->populateFromTitles($data); + $resultPageSet->populateFromTitles($titles); } } @@ -172,4 +176,4 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index d277ba0ec6..9a3d7c42ee 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -41,50 +41,60 @@ class ApiQuerySiteinfo extends ApiQueryBase { public function execute() { $params = $this->extractRequestParams(); + $done = array(); foreach( $params['prop'] as $p ) { switch ( $p ) { case 'general': - $this->appendGeneralInfo( $p ); + $fit = $this->appendGeneralInfo( $p ); break; case 'namespaces': - $this->appendNamespaces( $p ); + $fit = $this->appendNamespaces( $p ); break; case 'namespacealiases': - $this->appendNamespaceAliases( $p ); + $fit = $this->appendNamespaceAliases( $p ); break; case 'specialpagealiases': - $this->appendSpecialPageAliases( $p ); + $fit = $this->appendSpecialPageAliases( $p ); break; case 'magicwords': - $this->appendMagicWords( $p ); + $fit = $this->appendMagicWords( $p ); break; case 'interwikimap': $filteriw = isset( $params['filteriw'] ) ? $params['filteriw'] : false; - $this->appendInterwikiMap( $p, $filteriw ); + $fit = $this->appendInterwikiMap( $p, $filteriw ); break; case 'dbrepllag': - $this->appendDbReplLagInfo( $p, $params['showalldb'] ); + $fit = $this->appendDbReplLagInfo( $p, $params['showalldb'] ); break; case 'statistics': - $this->appendStatistics( $p ); + $fit = $this->appendStatistics( $p ); break; case 'usergroups': - $this->appendUserGroups( $p ); + $fit = $this->appendUserGroups( $p ); break; case 'extensions': - $this->appendExtensions( $p ); + $fit = $this->appendExtensions( $p ); break; case 'fileextensions': - $this->appendFileExtensions( $p ); + $fit = $this->appendFileExtensions( $p ); break; case 'rightsinfo': - $this->appendRightsInfo( $p ); + $fit = $this->appendRightsInfo( $p ); break; default : ApiBase :: dieDebug( __METHOD__, "Unknown prop=$p" ); } + if(!$fit) + { + # Abuse siprop as a query-continue parameter + # and set it to all unprocessed props + $this->setContinueEnumParameter('prop', implode('|', + array_diff($params['prop'], $done))); + break; + } + $done[] = $p; } } @@ -129,7 +139,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data['timezone'] = $tz; $data['timeoffset'] = $offset; - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendNamespaces( $property ) { @@ -151,7 +161,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { } $this->getResult()->setIndexedTagName( $data, 'ns' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendNamespaceAliases( $property ) { @@ -168,7 +178,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { } $this->getResult()->setIndexedTagName( $data, 'ns' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendSpecialPageAliases( $property ) { @@ -181,7 +191,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data[] = $arr; } $this->getResult()->setIndexedTagName( $data, 'specialpage' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendMagicWords( $property ) { @@ -197,7 +207,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data[] = $arr; } $this->getResult()->setIndexedTagName($data, 'magicword'); - $this->getResult()->addValue('query', $property, $data); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendInterwikiMap( $property, $filter ) { @@ -235,7 +245,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $db->freeResult( $res ); $this->getResult()->setIndexedTagName( $data, 'iw' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendDbReplLagInfo( $property, $includeAll ) { @@ -263,7 +273,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $result = $this->getResult(); $result->setIndexedTagName( $data, 'db' ); - $result->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendStatistics( $property ) { @@ -277,7 +287,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data['activeusers'] = intval( SiteStats::activeUsers() ); $data['admins'] = intval( SiteStats::numberingroup('sysop') ); $data['jobs'] = intval( SiteStats::jobs() ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendUserGroups( $property ) { @@ -290,7 +300,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { } $this->getResult()->setIndexedTagName( $data, 'group' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendFileExtensions( $property ) { @@ -301,7 +311,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data[] = array( 'ext' => $ext ); } $this->getResult()->setIndexedTagName( $data, 'fe' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendExtensions( $property ) { @@ -334,7 +344,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { } $this->getResult()->setIndexedTagName( $data, 'ext' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } @@ -352,7 +362,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { 'text' => $text ? $text : '' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } @@ -423,4 +433,4 @@ class ApiQuerySiteinfo extends ApiQueryBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php index 4da7d3120a..19a38c136f 100644 --- a/includes/api/ApiQueryUserContributions.php +++ b/includes/api/ApiQueryUserContributions.php @@ -83,7 +83,6 @@ class ApiQueryContributions extends ApiQueryBase { $res = $this->select( __METHOD__ ); //Initialise some variables - $data = array (); $count = 0; $limit = $this->params['limit']; @@ -99,16 +98,21 @@ class ApiQueryContributions extends ApiQueryBase { } $vals = $this->extractRowInfo($row); - if ($vals) - $data[] = $vals; + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + if($this->multiUserMode) + $this->setContinueEnumParameter('continue', $this->continueStr($row)); + else + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rev_timestamp)); + break; + } } //Free the database record so the connection can get on with other stuff $db->freeResult($res); - //And send the whole shebang out as output. - $this->getResult()->setIndexedTagName($data, 'item'); - $this->getResult()->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'item'); } /** @@ -358,4 +362,4 @@ class ApiQueryContributions extends ApiQueryBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php index e1b699a399..42bb15d53a 100644 --- a/includes/api/ApiQueryUsers.php +++ b/includes/api/ApiQueryUsers.php @@ -51,71 +51,72 @@ if (!defined('MEDIAWIKI')) { $this->prop = array(); } - if(is_array($params['users'])) { - $r = $this->getOtherUsersInfo($params['users']); - $result->setIndexedTagName($r, 'user'); - } - $result->addValue("query", $this->getModuleName(), $r); - } - - protected function getOtherUsersInfo($users) { - $goodNames = $retval = array(); + $users = (array)$params['users']; + $goodNames = $done = array(); + $result = $this->getResult(); // Canonicalize user names foreach($users as $u) { $n = User::getCanonicalName($u); if($n === false || $n === '') - $retval[] = array('name' => $u, 'invalid' => ''); + { + $vals = array('name' => $u, 'invalid' => ''); + $fit = $result->addValue(array('query', $this->getModuleName()), + null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('users', + implode('|', array_diff($users, $done))); + $goodNames = array(); + break; + } + $done[] = $u; + } else $goodNames[] = $n; } - if(!count($goodNames)) - return $retval; - - $db = $this->getDB(); - $this->addTables('user', 'u1'); - $this->addFields('u1.*'); - $this->addWhereFld('u1.user_name', $goodNames); - - if(isset($this->prop['groups'])) { - $this->addTables('user_groups'); - $this->addJoinConds(array('user_groups' => array('LEFT JOIN', 'ug_user=u1.user_id'))); - $this->addFields('ug_group'); - } - if(isset($this->prop['blockinfo'])) { - $this->addTables('ipblocks'); - $this->addTables('user', 'u2'); - $u2 = $this->getAliasedName('user', 'u2'); - $this->addJoinConds(array( - 'ipblocks' => array('LEFT JOIN', 'ipb_user=u1.user_id'), - $u2 => array('LEFT JOIN', 'ipb_by=u2.user_id'))); - $this->addFields(array('ipb_reason', 'u2.user_name blocker_name')); - } + if(count($goodNames)) + { + $db = $this->getDb(); + $this->addTables('user', 'u1'); + $this->addFields('u1.user_name'); + $this->addWhereFld('u1.user_name', $goodNames); + $this->addFieldsIf('u1.user_editcount', isset($this->prop['editcount'])); + $this->addFieldsIf('u1.user_registration', isset($this->prop['registration'])); + + if(isset($this->prop['groups'])) { + $this->addTables('user_groups'); + $this->addJoinConds(array('user_groups' => array('LEFT JOIN', 'ug_user=u1.user_id'))); + $this->addFields('ug_group'); + } + if(isset($this->prop['blockinfo'])) { + $this->addTables('ipblocks'); + $this->addTables('user', 'u2'); + $u2 = $this->getAliasedName('user', 'u2'); + $this->addJoinConds(array( + 'ipblocks' => array('LEFT JOIN', 'ipb_user=u1.user_id'), + $u2 => array('LEFT JOIN', 'ipb_by=u2.user_id'))); + $this->addFields(array('ipb_reason', 'u2.user_name blocker_name')); + } - $data = array(); - $res = $this->select(__METHOD__); - while(($r = $db->fetchObject($res))) { - $user = User::newFromRow($r); - $name = $user->getName(); - $data[$name]['name'] = $name; - if(isset($this->prop['editcount'])) - // No proper member function in User class for this - $data[$name]['editcount'] = $r->user_editcount; - if(isset($this->prop['registration'])) - // Nor for this one - $data[$name]['registration'] = wfTimestampOrNull(TS_ISO_8601, $r->user_registration); - if(isset($this->prop['groups'])) - // This row contains only one group, others will be added from other rows - if(!is_null($r->ug_group)) - $data[$name]['groups'][] = $r->ug_group; - if(isset($this->prop['blockinfo'])) - if(!is_null($r->blocker_name)) { - $data[$name]['blockedby'] = $r->blocker_name; - $data[$name]['blockreason'] = $r->ipb_reason; - } - if(isset($this->prop['emailable']) && $user->canReceiveEmail()) - $data[$name]['emailable'] = ''; + $data = array(); + $res = $this->select(__METHOD__); + while(($r = $db->fetchObject($res))) { + $data[$r->user_name]['name'] = $r->user_name; + if(isset($this->prop['editcount'])) + $data[$r->user_name]['editcount'] = $r->user_editcount; + if(isset($this->prop['registration'])) + $data[$r->user_name]['registration'] = wfTimestampOrNull(TS_ISO_8601, $r->user_registration); + if(isset($this->prop['groups'])) + // This row contains only one group, others will be added from other rows + if(!is_null($r->ug_group)) + $data[$r->user_name]['groups'][] = $r->ug_group; + if(isset($this->prop['blockinfo'])) + if(!is_null($r->blocker_name)) { + $data[$r->user_name]['blockedby'] = $r->blocker_name; + $data[$r->user_name]['blockreason'] = $r->ipb_reason; + } + } } - // Second pass: add result data to $retval foreach($goodNames as $u) { if(!isset($data[$u])) @@ -125,8 +126,9 @@ if (!defined('MEDIAWIKI')) { $this->getResult()->setIndexedTagName($data[$u]['groups'], 'g'); $retval[] = $data[$u]; } + $done[] = $u; } - return $retval; + return $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'user'); } public function getAllowedParams() { diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php index 7007a91e95..f620085794 100644 --- a/includes/api/ApiQueryWatchlist.php +++ b/includes/api/ApiQueryWatchlist.php @@ -168,7 +168,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->addOption('LIMIT', $params['limit'] +1); - $data = array (); + $ids = array (); $count = 0; $res = $this->select(__METHOD__); @@ -182,13 +182,18 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { if (is_null($resultPageSet)) { $vals = $this->extractRowInfo($row); - if ($vals) - $data[] = $vals; + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('start', + wfTimestamp(TS_ISO_8601, $row->rc_timestamp)); + break; + } } else { if ($params['allrev']) { - $data[] = intval($row->rc_this_oldid); + $ids[] = intval($row->rc_this_oldid); } else { - $data[] = intval($row->rc_cur_id); + $ids[] = intval($row->rc_cur_id); } } } @@ -196,13 +201,12 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $db->freeResult($res); if (is_null($resultPageSet)) { - $this->getResult()->setIndexedTagName($data, 'item'); - $this->getResult()->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'item'); } elseif ($params['allrev']) { - $resultPageSet->populateFromRevisionIDs($data); + $resultPageSet->populateFromRevisionIDs($ids); } else { - $resultPageSet->populateFromPageIDs($data); + $resultPageSet->populateFromPageIDs($ids); } } @@ -340,4 +344,4 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php index fd6192f76e..bbd92ee9e9 100644 --- a/includes/api/ApiQueryWatchlistRaw.php +++ b/includes/api/ApiQueryWatchlistRaw.php @@ -89,7 +89,6 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); $db = $this->getDB(); - $data = array(); $titles = array(); $count = 0; while($row = $db->fetchObject($res)) @@ -108,16 +107,19 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase { ApiQueryBase::addTitleInfo($vals, $t); if(isset($prop['changed']) && !is_null($row->wl_notificationtimestamp)) $vals['changed'] = wfTimestamp(TS_ISO_8601, $row->wl_notificationtimestamp); - $data[] = $vals; + $fit = $this->getResult()->addValue($this->getModuleName(), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('continue', $row->wl_namespace . '|' . + $this->keyToTitle($row->wl_title)); + break; + } } else $titles[] = $t; } if(is_null($resultPageSet)) - { - $this->getResult()->setIndexedTagName($data, 'wr'); - $this->getResult()->addValue(null, $this->getModuleName(), $data); - } + $this->getResult()->setIndexedTagName_internal($this->getModuleName(), 'wr'); else $resultPageSet->populateFromTitles($titles); } @@ -176,4 +178,4 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase { public function getVersion() { return __CLASS__ . ': $Id$'; } -} +} \ No newline at end of file diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php index ec823aa6b8..3a1585d505 100644 --- a/includes/api/ApiResult.php +++ b/includes/api/ApiResult.php @@ -47,7 +47,7 @@ if (!defined('MEDIAWIKI')) { */ class ApiResult extends ApiBase { - private $mData, $mIsRawMode; + private $mData, $mIsRawMode, $mSize, $mCheckingSize; /** * Constructor @@ -55,6 +55,7 @@ class ApiResult extends ApiBase { public function __construct($main) { parent :: __construct($main, 'result'); $this->mIsRawMode = false; + $this->mCheckingSize = true; $this->reset(); } @@ -63,6 +64,7 @@ class ApiResult extends ApiBase { */ public function reset() { $this->mData = array (); + $this->mSize = 0; } /** @@ -81,11 +83,52 @@ class ApiResult extends ApiBase { } /** - * Get result's internal data array + * Get the result's internal data array (read-only) */ - public function & getData() { + public function getData() { return $this->mData; } + + /** + * Get the 'real' size of a result item. This means the strlen() of the item, + * or the sum of the strlen()s of the elements if the item is an array. + * @param mixed $value + * @return int + */ + public static function size($value) { + $s = 0; + if(is_array($value)) + foreach($value as $v) + $s += self::size($v); + else if(!is_object($value)) + // Objects can't always be cast to string + $s = strlen($value); + return $s; + } + + /** + * Get the size of the result, i.e. the amount of bytes in it + * @return int + */ + public function getSize() { + return $this->mSize; + } + + /** + * Disable size checking in addValue(). Don't use this unless you + * REALLY know what you're doing. Values added while size checking + * was disabled will not be counted (ever) + */ + public function disableSizeCheck() { + $this->mCheckingSize = false; + } + + /** + * Re-enable size checking in addValue() + */ + public function enableSizeCheck() { + $this->mCheckingSize = true; + } /** * Add an output value to the array by name. @@ -156,15 +199,38 @@ class ApiResult extends ApiBase { } } + /** + * Calls setIndexedTagName() on an array already in the result. + * Don't specify a path to a value that's not in the result, or + * you'll get nasty errors. + * @param array $path Path to the array, like addValue()'s path + * @param string $tag + */ + public function setIndexedTagName_internal( $path, $tag ) { + $data = & $this->mData; + foreach((array)$path as $p) + $data = & $data[$p]; + if(is_null($data)) + return; + $this->setIndexedTagName($data, $tag); + } + /** * Add value to the output data at the given path. * Path is an indexed array, each element specifing the branch at which to add the new value * Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value * If $name is empty, the $value is added as a next list element data[] = $value + * @return bool True if $value fits in the result, false if not */ public function addValue($path, $name, $value) { - - $data = & $this->getData(); + global $wgAPIMaxResultSize; + $data = & $this->mData; + if( $this->mCheckingSize ) { + $newsize = $this->mSize + self::size($value); + if($newsize > $wgAPIMaxResultSize) + return false; + $this->mSize = $newsize; + } if (!is_null($path)) { if (is_array($path)) { @@ -184,6 +250,24 @@ class ApiResult extends ApiBase { $data[] = $value; // Add list element else ApiResult :: setElement($data, $name, $value); // Add named element + return true; + } + + /** + * Unset a value previously added to the result set. + * Fails silently if the value isn't found. + * For parameters, see addValue() + */ + public function unsetValue($path, $name) { + $data = & $this->mData; + if(!is_null($path)) + foreach((array)$path as $p) { + if(!isset($data[$p])) + return; + $data = & $data[$p]; + } + $this->mSize -= self::size($data[$name]); + unset($data[$name]); } /**