* API: BREAKING CHANGE: (bug 11430) Return fewer results than the limit in some cases...
authorRoan Kattouw <catrope@users.mediawiki.org>
Thu, 5 Feb 2009 14:30:59 +0000 (14:30 +0000)
committerRoan Kattouw <catrope@users.mediawiki.org>
Thu, 5 Feb 2009 14:30:59 +0000 (14:30 +0000)
* 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()

37 files changed:
includes/DefaultSettings.php
includes/api/ApiBase.php
includes/api/ApiFormatBase.php
includes/api/ApiMain.php
includes/api/ApiQuery.php
includes/api/ApiQueryAllCategories.php
includes/api/ApiQueryAllLinks.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryAllimages.php
includes/api/ApiQueryAllmessages.php
includes/api/ApiQueryAllpages.php
includes/api/ApiQueryBacklinks.php
includes/api/ApiQueryBase.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryCategories.php
includes/api/ApiQueryCategoryInfo.php
includes/api/ApiQueryCategoryMembers.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryDuplicateFiles.php
includes/api/ApiQueryExtLinksUsage.php
includes/api/ApiQueryExternalLinks.php
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryImages.php
includes/api/ApiQueryInfo.php
includes/api/ApiQueryLangLinks.php
includes/api/ApiQueryLinks.php
includes/api/ApiQueryLogEvents.php
includes/api/ApiQueryRandom.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryRevisions.php
includes/api/ApiQuerySearch.php
includes/api/ApiQuerySiteinfo.php
includes/api/ApiQueryUserContributions.php
includes/api/ApiQueryUsers.php
includes/api/ApiQueryWatchlist.php
includes/api/ApiQueryWatchlistRaw.php
includes/api/ApiResult.php

index 7012e1f..7135682 100644 (file)
@@ -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.
index 0922380..643a741 100644 (file)
@@ -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
index f6c2bb2..fc5ac51 100644 (file)
@@ -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
index 33e9ceb..7f3b4a7 100644 (file)
@@ -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
index 0ffabc3..468d350 100644 (file)
@@ -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
index b8c06e9..ea38742 100644 (file)
@@ -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
index 19d35d6..3238904 100644 (file)
@@ -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
index aeb0e2d..fd36b19 100644 (file)
@@ -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
index 040457e..a4962c8 100644 (file)
@@ -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);
                }
        }
 
index 96b131b..1c0b532 100644 (file)
@@ -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
index 8b22169..4391290 100644 (file)
@@ -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
index 0beb98a..8eec410 100644 (file)
@@ -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() {
index c8951a0..73f70eb 100644 (file)
@@ -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();
        }
 
        /**
index efee5b2..a2a77a1 100644 (file)
@@ -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
index badca7c..d6f6177 100644 (file)
@@ -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
index 1b578b5..115bd61 100644 (file)
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * This query adds <categories> subelement to all pages with the list of images embedded into those pages.
+ * This query adds the <categories> 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
index ba8a472..6e88012 100644 (file)
@@ -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
index 36490eb..8ef0e8e 100644 (file)
@@ -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
index f7171b8..67bae8b 100644 (file)
@@ -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);
        }
 
index c61e53a..d4dcb83 100644 (file)
@@ -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
index 0757405..b8591e0 100644 (file)
@@ -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
index 03edc35..e691001 100644 (file)
@@ -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',
                );
        }
 
index 4bee288..cc18276 100644 (file)
@@ -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
index 92d088c..202cc15 100644 (file)
@@ -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',
                );
        }
 
index 9c905e3..7aa9783 100644 (file)
@@ -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
index 4438c06..4cde3cf 100644 (file)
@@ -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
index f745894..6e29752 100644 (file)
@@ -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) {
index e7b8bf4..a8fc147 100644 (file)
@@ -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
index 10436c4..c900989 100644 (file)
@@ -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
index 258f460..c88d3f9 100644 (file)
@@ -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 '<rev>' 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',
                );
        }
 
index f79e3a0..a34c2f6 100644 (file)
@@ -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
index d277ba0..9a3d7c4 100644 (file)
@@ -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
index 4da7d31..19a38c1 100644 (file)
@@ -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
index e1b699a..42bb15d 100644 (file)
@@ -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() {
index 7007a91..f620085 100644 (file)
@@ -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
index fd6192f..bbd92ee 100644 (file)
@@ -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
index ec823aa..3a1585d 100644 (file)
@@ -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]);
        }
 
        /**