Merge "Protect WAN cache sets() against uncommitted data"
[lhc/web/wiklou.git] / includes / api / ApiQueryAllDeletedRevisions.php
index 0b1accb..8cb1119 100644 (file)
@@ -54,8 +54,6 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                $params = $this->extractRequestParams( false );
 
                $result = $this->getResult();
-               $pageSet = $this->getPageSet();
-               $titles = $pageSet->getTitles();
 
                // This module operates in two modes:
                // 'user': List deleted revs by a certain user
@@ -83,6 +81,21 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                        }
                }
 
+               // If we're generating titles only, we can use DISTINCT for a better
+               // query. But we can't do that in 'user' mode (wrong index), and we can
+               // only do it when sorting ASC (because MySQL apparently can't use an
+               // index backwards for grouping even though it can for ORDER BY, WTF?)
+               $dir = $params['dir'];
+               $optimizeGenerateTitles = false;
+               if ( $mode === 'all' && $params['generatetitles'] && $resultPageSet !== null ) {
+                       if ( $dir === 'newer' ) {
+                               $optimizeGenerateTitles = true;
+                       } else {
+                               $p = $this->getModulePrefix();
+                               $this->setWarning( "For better performance when generating titles, set {$p}dir=newer" );
+                       }
+               }
+
                $this->addTables( 'archive' );
                if ( $resultPageSet === null ) {
                        $this->parseParameters( $params );
@@ -90,7 +103,12 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                        $this->addFields( array( 'ar_title', 'ar_namespace' ) );
                } else {
                        $this->limit = $this->getParameter( 'limit' ) ?: 10;
-                       $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp', 'ar_rev_id', 'ar_id' ) );
+                       $this->addFields( array( 'ar_title', 'ar_namespace' ) );
+                       if ( $optimizeGenerateTitles ) {
+                               $this->addOption( 'DISTINCT' );
+                       } else {
+                               $this->addFields( array( 'ar_timestamp', 'ar_rev_id', 'ar_id' ) );
+                       }
                }
 
                if ( $this->fld_tags ) {
@@ -130,26 +148,68 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                        }
                }
 
-               $dir = $params['dir'];
                $miser_ns = null;
 
                if ( $mode == 'all' ) {
                        if ( $params['namespace'] !== null ) {
-                               $this->addWhereFld( 'ar_namespace', $params['namespace'] );
+                               $namespaces = $params['namespace'];
+                               $this->addWhereFld( 'ar_namespace', $namespaces );
+                       } else {
+                               $namespaces = MWNamespace::getValidNamespaces();
                        }
 
-                       $from = $params['from'] === null
-                               ? null
-                               : $this->titlePartToKey( $params['from'], $params['namespace'] );
-                       $to = $params['to'] === null
-                               ? null
-                               : $this->titlePartToKey( $params['to'], $params['namespace'] );
-                       $this->addWhereRange( 'ar_title', $dir, $from, $to );
+                       // For from/to/prefix, we have to consider the potential
+                       // transformations of the title in all specified namespaces.
+                       // Generally there will be only one transformation, but wikis with
+                       // some namespaces case-sensitive could have two.
+                       if ( $params['from'] !== null || $params['to'] !== null ) {
+                               $isDirNewer = ( $dir === 'newer' );
+                               $after = ( $isDirNewer ? '>=' : '<=' );
+                               $before = ( $isDirNewer ? '<=' : '>=' );
+                               $where = array();
+                               foreach ( $namespaces as $ns ) {
+                                       $w = array();
+                                       if ( $params['from'] !== null ) {
+                                               $w[] = 'ar_title' . $after .
+                                                       $db->addQuotes( $this->titlePartToKey( $params['from'], $ns ) );
+                                       }
+                                       if ( $params['to'] !== null ) {
+                                               $w[] = 'ar_title' . $before .
+                                                       $db->addQuotes( $this->titlePartToKey( $params['to'], $ns ) );
+                                       }
+                                       $w = $db->makeList( $w, LIST_AND );
+                                       $where[$w][] = $ns;
+                               }
+                               if ( count( $where ) == 1 ) {
+                                       $where = key( $where );
+                                       $this->addWhere( $where );
+                               } else {
+                                       $where2 = array();
+                                       foreach ( $where as $w => $ns ) {
+                                               $where2[] = $db->makeList( array( $w, 'ar_namespace' => $ns ), LIST_AND );
+                                       }
+                                       $this->addWhere( $db->makeList( $where2, LIST_OR ) );
+                               }
+                       }
 
                        if ( isset( $params['prefix'] ) ) {
-                               $this->addWhere( 'ar_title' . $db->buildLike(
-                                       $this->titlePartToKey( $params['prefix'], $params['namespace'] ),
-                                       $db->anyString() ) );
+                               $where = array();
+                               foreach ( $namespaces as $ns ) {
+                                       $w = 'ar_title' . $db->buildLike(
+                                               $this->titlePartToKey( $params['prefix'], $ns ),
+                                               $db->anyString() );
+                                       $where[$w][] = $ns;
+                               }
+                               if ( count( $where ) == 1 ) {
+                                       $where = key( $where );
+                                       $this->addWhere( $where );
+                               } else {
+                                       $where2 = array();
+                                       foreach ( $where as $w => $ns ) {
+                                               $where2[] = $db->makeList( array( $w, 'ar_namespace' => $ns ), LIST_AND );
+                                       }
+                                       $this->addWhere( $db->makeList( $where2, LIST_OR ) );
+                               }
                        }
                } else {
                        if ( $this->getConfig()->get( 'MiserMode' ) ) {
@@ -186,7 +246,14 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                if ( !is_null( $params['continue'] ) ) {
                        $cont = explode( '|', $params['continue'] );
                        $op = ( $dir == 'newer' ? '>' : '<' );
-                       if ( $mode == 'all' ) {
+                       if ( $optimizeGenerateTitles ) {
+                               $this->dieContinueUsageIf( count( $cont ) != 2 );
+                               $ns = intval( $cont[0] );
+                               $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
+                               $title = $db->addQuotes( $cont[1] );
+                               $this->addWhere( "ar_namespace $op $ns OR " .
+                                       "(ar_namespace = $ns AND ar_title $op= $title)" );
+                       } elseif ( $mode == 'all' ) {
                                $this->dieContinueUsageIf( count( $cont ) != 4 );
                                $ns = intval( $cont[0] );
                                $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
@@ -216,7 +283,13 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
 
                $sort = ( $dir == 'newer' ? '' : ' DESC' );
                $orderby = array();
-               if ( $mode == 'all' ) {
+               if ( $optimizeGenerateTitles ) {
+                       // Targeting index name_title_timestamp
+                       if ( $params['namespace'] === null || count( array_unique( $params['namespace'] ) ) > 1 ) {
+                               $orderby[] = "ar_namespace $sort";
+                       }
+                       $orderby[] = "ar_title $sort";
+               } elseif ( $mode == 'all' ) {
                        // Targeting index name_title_timestamp
                        if ( $params['namespace'] === null || count( array_unique( $params['namespace'] ) ) > 1 ) {
                                $orderby[] = "ar_namespace $sort";
@@ -240,7 +313,9 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                foreach ( $res as $row ) {
                        if ( ++$count > $this->limit ) {
                                // We've had enough
-                               if ( $mode == 'all' ) {
+                               if ( $optimizeGenerateTitles ) {
+                                       $this->setContinueEnumParameter( 'continue', "$row->ar_namespace|$row->ar_title" );
+                               } elseif ( $mode == 'all' ) {
                                        $this->setContinueEnumParameter( 'continue',
                                                "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
                                        );
@@ -276,7 +351,7 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                                                'pageid' => $title->getArticleID(),
                                                'revisions' => array( $rev ),
                                        );
-                                       $result->setIndexedTagName( $a['revisions'], 'rev' );
+                                       ApiResult::setIndexedTagName( $a['revisions'], 'rev' );
                                        ApiQueryBase::addTitleInfo( $a, $title );
                                        $fit = $result->addValue( array( 'query', $this->getModuleName() ), $index, $a );
                                } else {
@@ -305,7 +380,7 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                                $resultPageSet->populateFromRevisionIDs( $generated );
                        }
                } else {
-                       $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+                       $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
                }
        }
 
@@ -317,7 +392,6 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                        'namespace' => array(
                                ApiBase::PARAM_ISMULTI => true,
                                ApiBase::PARAM_TYPE => 'namespace',
-                               ApiBase::PARAM_DFLT => null,
                        ),
                        'start' => array(
                                ApiBase::PARAM_TYPE => 'timestamp',