Yaron Koren has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/386635 )

Change subject: Added "replacement table" feature for recreating data
......................................................................

Added "replacement table" feature for recreating data

Change-Id: I1ff6c5aeb5524c963126a3b622c6ff5c2c1d3330
---
M Cargo.css
M Cargo.php
M CargoUtils.php
M api/CargoRecreateTablesAPI.php
M drilldown/CargoSpecialDrilldown.php
M extension.json
M i18n/en.json
M i18n/qqq.json
M libs/ext.cargo.recreatedata.js
M maintenance/cargoRecreateData.php
M parserfunctions/CargoStore.php
M specials/CargoDeleteTable.php
M specials/CargoRecreateData.php
A specials/CargoSwitchTable.php
M specials/CargoTables.php
15 files changed, 408 insertions(+), 90 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Cargo 
refs/changes/35/386635/2

diff --git a/Cargo.css b/Cargo.css
index 5ccbd81..998e683 100644
--- a/Cargo.css
+++ b/Cargo.css
@@ -25,3 +25,10 @@
 span.searchmatch {
        font-weight: bold;
 }
+
+div.cargoReplacementTableInfo {
+       background: #ddd;
+       border: 1px solid #bbb;
+       padding: 6px;
+       margin-bottom: 10px;
+}
\ No newline at end of file
diff --git a/Cargo.php b/Cargo.php
index 4def3e0..9e89d77 100644
--- a/Cargo.php
+++ b/Cargo.php
@@ -99,6 +99,8 @@
 $wgAutoloadClasses['CargoTables'] = $dir . '/specials/CargoTables.php';
 $wgSpecialPages['DeleteCargoTable'] = 'CargoDeleteCargoTable';
 $wgAutoloadClasses['CargoDeleteCargoTable'] = $dir . 
'/specials/CargoDeleteTable.php';
+$wgSpecialPages['SwitchCargoTable'] = 'CargoSwitchCargoTable';
+$wgAutoloadClasses['CargoSwitchCargoTable'] = $dir . 
'/specials/CargoSwitchTable.php';
 $wgSpecialPages['ViewData'] = 'CargoViewData';
 $wgAutoloadClasses['CargoViewData'] = $dir . '/specials/CargoViewData.php';
 $wgAutoloadClasses['ViewDataPage'] = $dir . '/specials/CargoViewData.php';
@@ -182,8 +184,10 @@
                'dependencies' => 'mediawiki.jqueryMsg',
                'messages' => array(
                        'cargo-recreatedata-tablecreated',
+                       'cargo-recreatedata-replacementcreated',
                        'cargo-recreatedata-success',
-                       'cargo-cargotables-viewtablelink'
+                       'cargo-cargotables-viewtablelink',
+                       'cargo-cargotables-viewreplacementlink'
                ),
                'position' => 'bottom',
                'localBasePath' => __DIR__,
diff --git a/CargoUtils.php b/CargoUtils.php
index 08d6ba2..641a4a2 100644
--- a/CargoUtils.php
+++ b/CargoUtils.php
@@ -145,7 +145,8 @@
        static function getTableSchemas( $tableNames ) {
                $mainTableNames = array();
                foreach ( $tableNames as $tableName ) {
-                       if ( strpos( $tableName, '__' ) !== false ) {
+                       if ( strpos( $tableName, '__' ) !== false &&
+                               strpos( $tableName, '__NEXT' ) === false ) {
                                // We just want the first part of it.
                                $tableNameParts = explode( '__', $tableName );
                                $tableName = $tableNameParts[0];
@@ -440,7 +441,7 @@
         * @return boolean
         * @throws MWException
         */
-       public static function recreateDBTablesForTemplate( $templatePageID, 
$tableName = null ) {
+       public static function recreateDBTablesForTemplate( $templatePageID, 
$createReplacement, $tableName = null ) {
                $tableSchemaString = self::getPageProp( $templatePageID, 
'CargoFields' );
                // First, see if there even is DB storage for this template -
                // if not, exit.
@@ -456,29 +457,39 @@
                $dbw = wfGetDB( DB_MASTER );
                $cdb = self::getDB();
 
-               $tableNames = array();
-               $res = $dbw->select( 'cargo_tables', 'main_table', array( 
'template_id' => $templatePageID ) );
-               while ( $row = $dbw->fetchRow( $res ) ) {
-                       $tableNames[] = $row['main_table'];
+               // Cannot run any recreate if a replacement table exists.
+               $possibleReplacementTable = $tableName . '__NEXT';
+               if ( $cdb->tableExists( $possibleReplacementTable ) ) {
+                       throw new MWException( "Cannot currently recreate the 
table $tableName; the replacement table $possibleReplacementTable still 
exists." );
                }
 
-               // For whatever reason, that DB query might have failed -
-               // if so, just add the table name here.
-               if ( $tableName != null && !in_array( $tableName, $tableNames ) 
) {
-                       $tableNames[] = $tableName;
-               }
-
-               foreach( $tableNames as $curTable ) {
-                       try {
-                               $cdb->dropTable( $curTable );
-                       } catch ( Exception $e ) {
-                               throw new MWException( "Caught exception ($e) 
while trying to drop Cargo table. "
-                               . "Please make sure that your database user 
account has the DROP permission." );
+               if ( $createReplacement ) {
+                       $tableName .= '__NEXT';
+               } else {
+                       $tableNames = array();
+                       $res = $dbw->select( 'cargo_tables', 'main_table', 
array( 'template_id' => $templatePageID ) );
+                       while ( $row = $dbw->fetchRow( $res ) ) {
+                               $tableNames[] = $row['main_table'];
                        }
-                       $dbw->delete( 'cargo_pages', array( 'table_name' => 
$curTable ) );
-               }
 
-               $dbw->delete( 'cargo_tables', array( 'template_id' => 
$templatePageID ) );
+                       // For whatever reason, that DB query might have failed 
-
+                       // if so, just add the table name here.
+                       if ( $tableName != null && !in_array( $tableName, 
$tableNames ) ) {
+                               $tableNames[] = $tableName;
+                       }
+
+                       foreach( $tableNames as $curTable ) {
+                               try {
+                                       $cdb->dropTable( $curTable );
+                               } catch ( Exception $e ) {
+                                       throw new MWException( "Caught 
exception ($e) while trying to drop Cargo table. "
+                                       . "Please make sure that your database 
user account has the DROP permission." );
+                               }
+                               $dbw->delete( 'cargo_pages', array( 
'table_name' => $curTable ) );
+                       }
+
+                       $dbw->delete( 'cargo_tables', array( 'template_id' => 
$templatePageID ) );
+               }
 
                self::createCargoTableOrTables( $cdb, $dbw, $tableName, 
$tableSchema, $tableSchemaString, $templatePageID );
 
@@ -970,4 +981,4 @@
                        }
                }
        }
-}
+}
\ No newline at end of file
diff --git a/api/CargoRecreateTablesAPI.php b/api/CargoRecreateTablesAPI.php
index 5d02a7d..4ea25e1 100644
--- a/api/CargoRecreateTablesAPI.php
+++ b/api/CargoRecreateTablesAPI.php
@@ -24,10 +24,11 @@
                if ( $templateStr == '' ) {
                        $this->dieUsage( 'The template must be specified', 
'param_substr' );
                }
+               $createReplacement = $params['createReplacement'];
 
                $templateTitle = Title::makeTitleSafe( NS_TEMPLATE, 
$templateStr );
                $templatePageID = $templateTitle->getArticleID();
-               $success = CargoUtils::recreateDBTablesForTemplate( 
$templatePageID );
+               $success = CargoUtils::recreateDBTablesForTemplate( 
$templatePageID, $createReplacement );
 
                // Set top-level elements.
                $result = $this->getResult();
@@ -39,12 +40,16 @@
                        'template' => array(
                                ApiBase::PARAM_TYPE => 'string',
                        ),
+                       'createReplacement' => array(
+                               ApiBase::PARAM_TYPE => 'boolean',
+                       ),
                );
        }
 
        protected function getParamDescription() {
                return array(
                        'template' => 'The template whose declared Cargo 
table(s) should be recreated',
+                       'createReplacement' => 'Whether to put data into a 
replacement table',
                );
        }
 
diff --git a/drilldown/CargoSpecialDrilldown.php 
b/drilldown/CargoSpecialDrilldown.php
index e90ef4e..66891ee 100644
--- a/drilldown/CargoSpecialDrilldown.php
+++ b/drilldown/CargoSpecialDrilldown.php
@@ -54,6 +54,10 @@
                        $tableName = $tableNames[0];
                }
 
+               if ( $request->getCheck( '_replacement' ) ) {
+                       $tableName .= '__NEXT';
+               }
+
                $tableSchemas = CargoUtils::getTableSchemas( array( $tableName 
) );
                $all_filters = array();
                $fullTextSearchTerm = null;
@@ -173,7 +177,8 @@
        public $fullTextSearchTerm;
        public $searchablePages;
        public $searchableFiles;
-       public $showSingleTable = false;
+       private $showSingleTable = false;
+       private $isReplacementTable = false;
 
        /**
         * Initialize the variables of this page
@@ -214,6 +219,10 @@
                if ( $this->showSingleTable ) {
                        $url .= ( strpos( $url, '?' ) ) ? '&' : '?';
                        $url .= "_single";
+               }
+               if ( $this->isReplacementTable ) {
+                       $url .= ( strpos( $url, '?' ) ) ? '&' : '?';
+                       $url .= "_replacement";
                }
 
                if ( $searchTerm != null ) {
@@ -453,7 +462,7 @@
                $or_values[] = '_none';
                foreach ( $or_values as $i => $value ) {
                        if ( $i > 0 ) {
-                               $results_line .= " · ";
+                               $results_line .= " · ";
                        }
                        $filter_text = $this->printFilterValue( $af->filter, 
$value );
                        $applied_filters = $this->applied_filters;
@@ -543,7 +552,7 @@
                $num_printed_values = 0;
                foreach ( $filter_values as $value_str => $num_results ) {
                        if ( $num_printed_values++ > 0 ) {
-                               $results_line .= " · ";
+                               $results_line .= " · ";
                        }
                        $filter_url = $cur_url . urlencode( str_replace( ' ', 
'_', $f->name ) ) . '=' .
                                urlencode( str_replace( ' ', '_', $value_str ) 
);
@@ -575,7 +584,7 @@
                                if ( $node->mLeft !== 1 && 
$node->mWithinTreeMatchCount > 0 ) {
                                        // check if its not __pseudo_root__ 
node, then only print
                                        if ( $num_printed_values_level++ > 0 ) {
-                                               $results_line .= " · ";
+                                               $results_line .= " · ";
                                        }
                                        // generate a url to encode WITHIN 
search information by a "~within_" prefix in value_str
                                        $filter_url = $cur_url . urlencode( 
str_replace( ' ', '_', $f->name ) ) . '=' .
@@ -1108,9 +1117,15 @@
                }
 
                $header = "";
-               $this->showSingleTable = $wgRequest->getCheck( '_single' );
+               $this->isReplacementTable = $wgRequest->getCheck( 
'_replacement' );
+               $this->showSingleTable = $this->isReplacementTable || 
$wgRequest->getCheck( '_single' );
                if ( !$this->showSingleTable ) {
                        $header .= $this->printTablesList( $tables );
+               }
+               if ( $this->isReplacementTable ) {
+                       $this->tableName = str_replace( '__NEXT', '', 
$this->tableName );
+                       $text = "This table is a possible replacement for the 
{$this->tableName} table. It is not yet being used for querying.";
+                       $header .= Html::rawElement( 'div', array( 'class' => 
'warningbox' ), $text );
                }
 
                $displaySearchInput = ( $this->tableName == '_fileData' &&
@@ -1254,6 +1269,9 @@
                if ( $this->showSingleTable ) {
                        $params['_single'] = null;
                }
+               if ( $this->isReplacementTable ) {
+                       $params['_replacement'] = null;
+               }
                $params['_table'] = $this->tableName;
                if ( $this->fullTextSearchTerm != '' ) {
                        $params['_search'] = $this->fullTextSearchTerm;
@@ -1384,7 +1402,7 @@
                                'LEFT OUTER JOIN',
                                CargoUtils::escapedFieldName( $cdb, 
$mainTableName, '_pageID' ) .
                                ' = ' .
-                               CargoUtils::escapedFieldName( $cdb, 
$fileTableName, '_pageID' ) 
+                               CargoUtils::escapedFieldName( $cdb, 
$fileTableName, '_pageID' )
                        );
                        $tableNames[] = '_fileData';
                        $joinConds['_fileData'] = array(
diff --git a/extension.json b/extension.json
index bd5d0d5..629d971 100755
--- a/extension.json
+++ b/extension.json
@@ -17,6 +17,7 @@
        "SpecialPages": {
                "CargoTables": "CargoTables",
                "DeleteCargoTable": "CargoDeleteCargoTable",
+               "SwitchCargoTable": "CargoSwitchCargoTable",
                "ViewData": "CargoViewData",
                "CargoExport": "CargoExport",
                "PageValues": "CargoPageValues",
@@ -62,6 +63,7 @@
                "CargoRecreateData": "specials/CargoRecreateData.php",
                "CargoTables": "specials/CargoTables.php",
                "CargoDeleteCargoTable": "specials/CargoDeleteTable.php",
+               "CargoSwitchCargoTable": "specials/CargoSwitchTable.php",
                "CargoViewData": "specials/CargoViewData.php",
                "ViewDataPage": "specials/CargoViewData.php",
                "CargoExport": "specials/CargoExport.php",
@@ -123,8 +125,10 @@
                        "dependencies": "mediawiki.jqueryMsg",
                        "messages": [
                                "cargo-recreatedata-tablecreated",
+                               "cargo-recreatedata-replacementcreated",
                                "cargo-recreatedata-success",
-                               "cargo-cargotables-viewtablelink"
+                               "cargo-cargotables-viewtablelink",
+                               "cargo-cargotables-viewreplacementlink"
                        ],
                        "position": "bottom"
                },
diff --git a/i18n/en.json b/i18n/en.json
index 7336306..391e841 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -23,6 +23,7 @@
        "apihelp-cargorecreatetables-description": "Recreates one or more Cargo 
tables.",
        "apihelp-cargorecreatetables-summary": "Recreates one or more Cargo 
tables.",
        "apihelp-cargorecreatetables-param-template": "The template whose 
declared Cargo table(s) should be recreated",
+       "apihelp-cargorecreatetables-param-createReplacement": "Whether to put 
data into a replacement table",
        "apihelp-cargorecreatedata-description": "Re-populates the data in a 
Cargo table.",
        "apihelp-cargorecreatedata-summary": "Re-populates the data in a Cargo 
table.",
        "apihelp-cargorecreatedata-param-template": "The template whose data to 
use",
@@ -41,6 +42,7 @@
        "cargo-recreatedata-desc": "Recreate Cargo data for this template?",
        "cargo-recreatedata-createdata": "Create Cargo data for this template?",
        "cargo-recreatedata-tablecreated": "Recreated Cargo table \"$1\".",
+       "cargo-recreatedata-replacementcreated": "Recreated replacement table 
for Cargo table \"$1\".",
        "cargo-recreatedata-success": "The data is being recreated.",
        "viewdata": "View data",
        "cargo-viewdata-tables": "Table(s):",
@@ -57,13 +59,17 @@
        "cargo-cargotables-tablelist": "The following {{PLURAL:$1|table 
is|tables are}} defined:",
        "cargo-cargotables-viewtable": "View table: $1",
        "cargo-cargotables-viewtablelink": "View table",
+       "cargo-cargotables-viewreplacement": "View replacement table for $1",
+       "cargo-cargotables-viewreplacementlink": "View replacement table",
        "cargo-cargotables-notdeclared": "not defined by any template",
        "cargo-cargotables-declaredby": "Declared by $1",
        "cargo-cargotables-attachedby": "attached by $1",
        "cargo-cargotables-tablenotfound": "Table \"$1\" not found in Cargo 
database.",
        "cargo-cargotables-totalrows": "This table has '''$1''' 
{{PLURAL:$1|row|rows}} altogether.",
        "cargo-cargotables-totalrowsshort": "$1 {{PLURAL:$1|row|rows}}",
+       "cargo-cargotables-switch": "Switch to using this table.",
        "deletecargotable": "Delete Cargo table",
+       "switchcargotable": "Switch in replacement Cargo table",
        "pagevalues": "Page values",
        "cargo-pagevaluesfor": "Page values for \"$1\"",
        "cargo-pagevalues-tablevalues": "\"$1\" values",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index f1cc8e2..1f9d1ff 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -29,6 +29,7 @@
        "apihelp-cargorecreatetables-description": 
"{{doc-apihelp-description|cargorecreatetables}}",
        "apihelp-cargorecreatetables-summary": 
"{{doc-apihelp-summary|cargorecreatetables}}",
        "apihelp-cargorecreatetables-param-template": 
"{{doc-apihelp-param|cargorecreatetables|template}}",
+       "apihelp-cargorecreatetables-param-createReplacement": 
"{{doc-apihelp-param|cargorecreatetables|createReplacement}}",
        "apihelp-cargorecreatedata-description": 
"{{doc-apihelp-description|cargorecreatedata}}",
        "apihelp-cargorecreatedata-summary": 
"{{doc-apihelp-summary|cargorecreatedata}}",
        "apihelp-cargorecreatedata-param-template": 
"{{doc-apihelp-param|cargorecreatedata|template}}",
@@ -47,6 +48,7 @@
        "cargo-recreatedata-desc": "Label before a 'submit' button. 
({{doc-cargo}})",
        "cargo-recreatedata-createdata": "Label before a 'submit' button. 
({{doc-cargo}})",
        "cargo-recreatedata-tablecreated": "Informational message after success 
of creation of the table. ({{doc-cargo}})",
+       "cargo-recreatedata-replacementcreated": "Informational message after 
success of creation of a table. ({{doc-cargo}})",
        "cargo-recreatedata-success": "Message displayed after user submits a 
\"recreate data\" form.",
        "viewdata": "{{doc-special|ViewData}}",
        "cargo-viewdata-tables": "Label in a form to set the names of one or 
more tables to query.\n{{Identical|Table}}",
@@ -63,13 +65,17 @@
        "cargo-cargotables-tablelist": "Used as a page header.",
        "cargo-cargotables-viewtable": "Parameters:\n* $1 - table name",
        "cargo-cargotables-viewtablelink": "The text of a link to a subpage of 
[[Special:CargoTables]].",
+       "cargo-cargotables-viewreplacement": "Parameters:\n* $1 - table name",
+       "cargo-cargotables-viewreplacementlink": "The text of a link to a 
subpage of [[Special:CargoTables]].",
        "cargo-cargotables-notdeclared": "Descriptive message placed in 
[[Special:CargoTables]].",
        "cargo-cargotables-declaredby": "Parameters:\n* $1 - template name",
        "cargo-cargotables-attachedby": "Parameters:\n* $1 - template name",
        "cargo-cargotables-tablenotfound": "An error message. 
({{doc-cargo}})\n\nParameters:\n* $1 - table name.",
        "cargo-cargotables-totalrows": "Parameters:\n* $1 - number of rows in a 
database table.",
        "cargo-cargotables-totalrowsshort": "Parameters:\n* $1 - number of rows 
in a database table.",
+       "cargo-cargotables-switch": "The text of a link to a subpage of 
[[Special:SwitchCargoTable]]."
        "deletecargotable": "{{doc-special|DeleteCargoTable}}\n{{doc-cargo}}",
+       "switchcargotable": "{{doc-special|SwitchCargoTable}}\n{{doc-cargo}}",
        "pagevalues": "{{doc-special|PageValues}}",
        "cargo-pagevaluesfor": "Parameters:\n* $1 - a page name.",
        "cargo-pagevalues-tablevalues": "Parameters:\n* $1 - a database table 
name.",
diff --git a/libs/ext.cargo.recreatedata.js b/libs/ext.cargo.recreatedata.js
index 51c3fcb..26988ab 100644
--- a/libs/ext.cargo.recreatedata.js
+++ b/libs/ext.cargo.recreatedata.js
@@ -18,6 +18,7 @@
        var tableName = dataDiv.attr("tablename");
        var isDeclared = dataDiv.attr("isdeclared");
        var viewTableURL = dataDiv.attr("viewtableurl");
+       var createReplacement = false;
        var templateData = jQuery.parseJSON( dataDiv.html() );
 
        var numTotalPages = 0;
@@ -67,25 +68,33 @@
                                        recreateData.createJobs( templateNum + 
1, 0, replaceOldRows );
                                } else {
                                        // We're done.
-                                       $("#recreateDataProgress").html( "<p>" 
+ mw.msg( 'cargo-recreatedata-success' ) + "</p><p><a href=\"" + viewTableURL + 
"\">" + mw.msg( 'cargo-cargotables-viewtablelink' ) + "</a>.</p>" );
+                                       if ( createReplacement ) {
+                                               viewTableURL += "?_replacement";
+                                       }
+                                       var linkMsg = createReplacement ? 
'cargo-cargotables-viewreplacementlink' : 'cargo-cargotables-viewtablelink';
+                                       $("#recreateDataProgress").html( "<p>" 
+ mw.msg( 'cargo-recreatedata-success' ) + "</p><p><a href=\"" + viewTableURL + 
"\">" + mw.msg( linkMsg ) + "</a>.</p>" );
                                }
                        }
                });
        }
 
        jQuery( "#cargoSubmit" ).click( function() {
+               createReplacement = $("#createReplacement").is( ":checked" );
                recreateData.replaceForm();
 
                if ( isDeclared ) {
                        $("#recreateTableProgress").html( "<img src=\"" + 
cargoScriptPath + "/skins/loading.gif\" />" );
-                       $.get(
-                               apiURL, {
-                                       action: "cargorecreatetables",
-                                       template: templateData[0].name
-                               }
-                       )
+                       var queryStringData = {
+                               action: "cargorecreatetables",
+                               template: templateData[0].name,
+                       };
+                       if ( createReplacement ) {
+                               queryStringData.createReplacement = true;
+                       }
+                       $.get( apiURL, queryStringData )
                        .done(function( msg ) {
-                               $("#recreateTableProgress").html( "<p>" + 
mw.msg( 'cargo-recreatedata-tablecreated', tableName ) + "</p>" );
+                               var displayMsg = createReplacement ? 
'cargo-recreatedata-replacementcreated' : 'cargo-recreatedata-tablecreated';
+                               $("#recreateTableProgress").html( "<p>" + 
mw.msg( displayMsg, tableName ) + "</p>" );
                                recreateData.createJobs( 0, 0, false );
                        });
                } else {
@@ -97,4 +106,4 @@
        // is calling this code.
        recreateData.prototype = recreateData;
 
-} )( jQuery, mediaWiki, cargo );
+} )( jQuery, mediaWiki, cargo );
\ No newline at end of file
diff --git a/maintenance/cargoRecreateData.php 
b/maintenance/cargoRecreateData.php
index ca04fec..d0df0bd 100644
--- a/maintenance/cargoRecreateData.php
+++ b/maintenance/cargoRecreateData.php
@@ -45,6 +45,7 @@
                }
                $this->mDescription = "Recreate the data for one or more Cargo 
database tables.";
                $this->addOption( 'table', 'The Cargo table to recreate', 
false, true );
+               $this->addOption( 'replacement', 'Put all new data into a 
replacement table, to be switched in later' );
        }
 
        public function execute() {
@@ -53,6 +54,8 @@
                $this->templatesThatAttachToTables = 
CargoUtils::getAllPageProps( 'CargoAttachedTable' );
 
                $tableName = $this->getOption( 'table' );
+               $createReplacement = $this->hasOption( 'replacement' );
+
                if ( $tableName == null ) {
                        $tableNames = CargoUtils::getTables();
                        foreach ( $tableNames as $i => $tableName ) {
@@ -63,14 +66,14 @@
                                if ( $i > 0 && !$quiet ) {
                                        print "\n";
                                }
-                               $this->recreateAllDataForTable( $tableName );
+                               $this->recreateAllDataForTable( $tableName, 
$createReplacement );
                        }
                } else {
-                       $this->recreateAllDataForTable( $tableName );
+                       $this->recreateAllDataForTable( $tableName, 
$createReplacement );
                }
        }
 
-       function recreateAllDataForTable( $tableName ) {
+       function recreateAllDataForTable( $tableName, $createReplacement ) {
                $quiet = $this->getOption( 'quiet' );
 
                // Quick retrieval and check before we do anything else.
@@ -80,9 +83,11 @@
                        return;
                }
 
-
                if ( !$quiet ) {
                        print "Recreating data for Cargo table $tableName in 5 
seconds... hit [Ctrl]-C to escape.\n";
+                       if ( $createReplacement ) {
+                               print "(Data will be placed in a separate, 
replacement table.)\n";
+                       }
                        // No point waiting if the user doesn't know about it
                        // anyway, right?
                        sleep( 5 );
@@ -91,7 +96,13 @@
                if ( !$quiet ) {
                        print "Deleting and recreating table...\n";
                }
-               CargoUtils::recreateDBTablesForTemplate( $templatePageID, 
$tableName );
+
+               try {
+                       CargoUtils::recreateDBTablesForTemplate( 
$templatePageID, $createReplacement, $tableName );
+               } catch ( MWException $e ) {
+                       print "Error: " . $e->getMessage() . "\n";
+                       return;
+               }
 
                // These arrays are poorly named. @TODO - fix names
                if ( array_key_exists( $tableName, 
$this->templatesThatDeclareTables ) ) {
diff --git a/parserfunctions/CargoStore.php b/parserfunctions/CargoStore.php
index bdcecba..6dd0932 100644
--- a/parserfunctions/CargoStore.php
+++ b/parserfunctions/CargoStore.php
@@ -88,6 +88,13 @@
                                return;
                        }
                }
+          
+               // Always store data in the replacement table if it exists.
+               $cdb = CargoUtils::getDB();
+               if ( $cdb->tableExists( $tableName . '__NEXT' ) ) {
+                       $tableName .=  '__NEXT';
+               }
+               $cdb->close();
 
                // Get the declaration of the table.
                $dbw = wfGetDB( DB_MASTER );
diff --git a/specials/CargoDeleteTable.php b/specials/CargoDeleteTable.php
index 4ec9b0a..7278097 100644
--- a/specials/CargoDeleteTable.php
+++ b/specials/CargoDeleteTable.php
@@ -53,6 +53,7 @@
 
        function execute( $subpage = false ) {
                $out = $this->getOutput();
+               $req = $this->getRequest();
 
                $this->setHeaders();
                if ( $subpage == '' ) {
@@ -61,12 +62,20 @@
                        return true;
                }
 
+               $replacementTable = $req->getCheck( '_replacement' );
+               $origTableName = $subpage;
+               if ( $replacementTable ) {
+                       $tableName = $subpage . '__NEXT';
+               } else {
+                       $tableName = $subpage;
+               }
+
                // Make sure that this table exists.
                $dbr = wfGetDB( DB_SLAVE );
                $res = $dbr->select( 'cargo_tables', array( 'main_table', 
'field_tables', 'field_helper_tables' ),
-                       array( 'main_table' => $subpage ) );
+                       array( 'main_table' => $tableName ) );
                if ( $res->numRows() == 0 ) {
-                       $out->addHTML( CargoUtils::formatError( "Error: no 
table found named \"$subpage\"." ) );
+                       $out->addHTML( CargoUtils::formatError( "Error: no 
table found named \"$tableName\"." ) );
                        return true;
                }
 
@@ -76,8 +85,8 @@
                $fieldHelperTables = unserialize( $row['field_helper_tables'] );
 
                if ( $this->getRequest()->getCheck( 'delete' ) ) {
-                       self::deleteTable( $subpage, $fieldTables, 
$fieldHelperTables );
-                       $text = Html::element( 'p', null, "The table 
\"$subpage\" has been deleted." ) . "\n";
+                       self::deleteTable( $tableName, $fieldTables, 
$fieldHelperTables );
+                       $text = Html::element( 'p', null, "The table 
\"$tableName\" has been deleted." ) . "\n";
                        if ( method_exists( $this, 'getLinkRenderer' ) ) {
                                $linkRenderer = $this->getLinkRenderer();
                        } else {
@@ -90,13 +99,21 @@
                }
 
                $ctURL = $ctPage->getTitle()->getLocalURL();
-               $tableLink = Html::element( 'a', array( 'href' => 
"$ctURL/$subpage", ), $subpage );
+               $tableLink = Html::element( 'a', array( 'href' => 
"$ctURL/$origTableName", ), $origTableName );
 
-               $text = Html::rawElement( 'p', null, "Delete the Cargo table 
\"$tableLink\"?" );
+               if ( $replacementTable ) {
+                       $replacementTableURL = "$ctURL/$origTableName";
+                       $replacementTableURL .= ( strpos( $replacementTableURL, 
'?' ) ) ? '&' : '?';
+                       $replacementTableURL .= '_replacement';
+                       $replacementTableLink = Html::element( 'a', array( 
'href' => $replacementTableURL, ), 'replacement table' );
+                       $text = Html::rawElement( 'p', null, "Delete the 
$replacementTableLink for Cargo table \"$tableLink\"?" );
+               } else {
+                       $text = Html::rawElement( 'p', null, "Delete the Cargo 
table \"$tableLink\"?" );
+               }
                $formText = Xml::submitButton( $this->msg( 'delete' ), array( 
'name' => 'delete' ) );
                $text .= Html::rawElement( 'form', array( 'method' => 'post' ), 
$formText );
                $out->addHTML( $text );
 
                return true;
        }
-}
+}
\ No newline at end of file
diff --git a/specials/CargoRecreateData.php b/specials/CargoRecreateData.php
index 840f075..d6654d0 100644
--- a/specials/CargoRecreateData.php
+++ b/specials/CargoRecreateData.php
@@ -99,6 +99,10 @@
 
                // Simple form.
                $text .= '<div id="recreateDataCanvas">' . "\n";
+               if ( $tableExists ) {
+                       $text .= Html::rawElement( 'p', null, Html::check( 
'createReplacement', true, array( 'id' => 'createReplacement' ) ) .
+                               ' ' . "Recreate data into a replacement table, 
keeping the old one for querying" );
+               }
                $msg = $tableExists ? 'cargo-recreatedata-desc' : 
'cargo-recreatedata-createdata';
                $text .= Html::element( 'p', null, $this->msg( $msg )->parse() 
);
                $text .= Html::element( 'button', array( 'id' => 'cargoSubmit' 
), $this->msg( 'ok' )->parse() );
diff --git a/specials/CargoSwitchTable.php b/specials/CargoSwitchTable.php
new file mode 100644
index 0000000..3e4e9da
--- /dev/null
+++ b/specials/CargoSwitchTable.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * An interface to delete a "Cargo table", which can be one or more real
+ * database tables.
+ *
+ * The class is called CargoSwitchCargoTable, the file is called
+ * CargoSwitchTable.php, and the wiki page is Special:SwitchCargoTable...
+ * sorry for the confusion!
+ *
+ * @author Yaron Koren
+ * @ingroup Cargo
+ */
+
+class CargoSwitchCargoTable extends UnlistedSpecialPage {
+
+       function __construct() {
+               parent::__construct( 'SwitchCargoTable', 'recreatecargodata' );
+       }
+
+       public function doesWrites() {
+               return true;
+       }
+
+       /**
+        * The table being switched here is a Cargo table, not a DB table per
+        * se - a Cargo table corresponds to a main DB table, plus
+        * potentially one or more helper tables; all need to be switched.
+        * Also, records need to be removed, and modified, in the cargo_tables 
and
+        * cargo_pages tables.
+        */
+       public static function switchInTableReplacement( $mainTable, 
$fieldTables, $fieldHelperTables ) {
+               $cdb = CargoUtils::getDB();
+               try {
+                       $cdb->dropTable( $mainTable );
+                       $cdb->query( 'ALTER TABLE ' .
+                               $cdb->tableName( $mainTable . '__NEXT' ) .
+                               ' RENAME TO ' . $cdb->tableName( $mainTable ) );
+                       // The helper tables' names come from the database,
+                       // so they already contain '__NEXT' - remove that,
+                       // instead of adding it, when getting table names.
+                       foreach ( $fieldTables as $fieldTable ) {
+                               $origFieldTable = str_replace( '__NEXT', '', 
$fieldTable );
+                               $cdb->dropTable( $origFieldTable );
+                               $fieldTableName = $cdb->tableName( $fieldTable 
);
+                               $cdb->query( 'ALTER TABLE ' .
+                                       $cdb->tableName( $fieldTable ) .
+                                       ' RENAME TO ' .
+                                       $cdb->tableName( $origFieldTable ) );
+                       }
+                       if ( is_array( $fieldHelperTables ) ) {
+                               foreach ( $fieldHelperTables as 
$fieldHelperTable ) {
+                                       $origFieldHelperTable = str_replace( 
'__NEXT', '', $fieldHelperTable );
+                                       $cdb->dropTable( $origFieldHelperTable 
);
+                                       $cdb->query( 'ALTER TABLE ' .
+                                               $cdb->tableName( 
$fieldHelperTable ) .
+                                               ' RENAME TO ' .
+                                               $cdb->tableName( 
$origFieldHelperTable ) );
+                               }
+                       }
+               } catch ( Exception $e ) {
+                       throw new MWException( "Caught exception ($e) while 
trying to switch in replacement for Cargo table. "
+                       . "Please make sure that your database user account has 
the DROP permission." );
+               }
+               $cdb->close();
+
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->delete( 'cargo_tables', array( 'main_table' => $mainTable 
) );
+               $dbw->delete( 'cargo_pages', array( 'table_name' => $mainTable 
) );
+               $dbw->query( 'UPDATE cargo_tables SET main_table = \'' . 
$mainTable . '\' WHERE main_table = \'' . $mainTable . '__NEXT\'' );
+               $dbw->query( 'UPDATE cargo_pages SET table_name = \'' . 
$mainTable . '\' WHERE table_name = \'' . $mainTable . '__NEXT\'' );
+       }
+
+       function execute( $subpage = false ) {
+               $out = $this->getOutput();
+               $req = $this->getRequest();
+               $tableName = $subpage;
+
+               $this->setHeaders();
+               if ( $tableName == '' ) {
+                       /** @todo i18n for these error messages */
+                       $out->addHTML( CargoUtils::formatError( "Error: table 
name must be set." ) );
+                       return true;
+               }
+
+               // Make sure that this table, and its replacement, both exist.
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( 'cargo_tables', array( 'main_table', 
'field_tables', 'field_helper_tables' ),
+                       array( 'main_table' => $tableName ) );
+               if ( $res->numRows() == 0 ) {
+                       $out->addHTML( CargoUtils::formatError( "Error: no 
table found named \"$tableName\"." ) );
+                       return true;
+               }
+               $res = $dbr->select( 'cargo_tables', array( 'main_table', 
'field_tables', 'field_helper_tables' ),
+                       array( 'main_table' => $tableName . '__NEXT' ) );
+               if ( $res->numRows() == 0 ) {
+                       $out->addHTML( CargoUtils::formatError( "Error: no 
table found named \"$tableName" . "__NEXT\"." ) );
+                       return true;
+               }
+
+               $ctPage = SpecialPageFactory::getPage( 'CargoTables' );
+               $row = $res->fetchRow();
+               $fieldTables = unserialize( $row['field_tables'] );
+               $fieldHelperTables = unserialize( $row['field_helper_tables'] );
+
+               if ( $this->getRequest()->getCheck( 'switch' ) ) {
+                       self::switchInTableReplacement( $tableName, 
$fieldTables, $fieldHelperTables );
+                       $text = Html::element( 'p', null, "The replacement for 
the table \"$tableName\" was switched in." ) . "\n";
+                       if ( method_exists( $this, 'getLinkRenderer' ) ) {
+                               $linkRenderer = $this->getLinkRenderer();
+                       } else {
+                               $linkRenderer = null;
+                       }
+                       $tablesLink = CargoUtils::makeLink( $linkRenderer, 
$ctPage->getTitle(), $ctPage->getDescription() );
+                       $text .= Html::rawElement( 'p', null, $this->msg( 
'returnto', $tablesLink )->text() );
+                       $out->addHTML( $text );
+                       return true;
+               }
+
+               $ctURL = $ctPage->getTitle()->getLocalURL();
+               $tableLink = Html::element( 'a', array( 'href' => 
"$ctURL/$tableName", ), $tableName );
+
+               $text = Html::rawElement( 'p', null, "Switch in the replacement 
for the Cargo table \"$tableLink\"?" );
+               $formText = Xml::submitButton( 'Switch', array( 'name' => 
'switch' ) );
+               $text .= Html::rawElement( 'form', array( 'method' => 'post' ), 
$formText );
+               $out->addHTML( $text );
+
+               return true;
+       }
+
+}
\ No newline at end of file
diff --git a/specials/CargoTables.php b/specials/CargoTables.php
index ce302c7..76ac859 100644
--- a/specials/CargoTables.php
+++ b/specials/CargoTables.php
@@ -18,6 +18,8 @@
 
        function execute( $tableName ) {
                $out = $this->getOutput();
+               $req = $this->getRequest();
+               $user = $this->getUser();
                $this->setHeaders();
 
                if ( $tableName == '' ) {
@@ -25,7 +27,34 @@
                        return;
                }
 
-               $pageTitle = $this->msg( 'cargo-cargotables-viewtable', 
$tableName )->parse();
+               $cdb = CargoUtils::getDB();
+
+               $ctPage = SpecialPageFactory::getPage( 'CargoTables' );
+               $ctURL = $ctPage->getTitle()->getFullURL();
+               $viewURL = "$ctURL/$tableName";
+
+               if ( $req->getCheck( '_replacement' ) ) {
+                       global $wgScriptPath;
+                       $pageTitle = $this->msg( 
'cargo-cargotables-viewreplacement', '"' . $tableName . '"' )->parse();
+                       $tableLink = Html::element( 'a', array( 'href' => 
$viewURL ), $tableName );
+                       $text = "This table is a possible replacement for the 
$tableLink table. It is not yet being used for querying.";
+                       if ( $user->isAllowed( 'recreatecargodata' ) ) {
+                               $sctPage = SpecialPageFactory::getPage( 
'SwitchCargoTable' );
+                               $switchURL = $sctPage->getTitle()->getFullURL() 
. "/$tableName";
+                               $text .= ' ' . Html::element( 'a', array( 
'href' => $switchURL ),
+                                       $this->msg( "cargo-cargotables-switch" 
)->parse() );
+                       }
+                       $out->addHtml( Html::rawElement( 'div', array( 'class' 
=> 'warningbox' ), $text ) );
+                       $tableName .= '__NEXT';
+               } else {
+                       $pageTitle = $this->msg( 'cargo-cargotables-viewtable', 
$tableName )->parse();
+                       if ( $cdb->tableExists( $tableName . '__NEXT' ) ) {
+                               global $wgScriptPath;
+                               $text = "This table is currently read-only, 
while a replacement table for it is generated.";
+                               $out->addHtml( Html::rawElement( 'div', array( 
'class' => 'warningbox' ), $text ) );
+                       }
+               }
+
                $out->setPageTitle( $pageTitle );
 
                // Mimic the appearance of a subpage to link back to
@@ -42,8 +71,6 @@
                        htmlspecialchars( $ctPage->getDescription() )
                );
                $out->setSubtitle( '< '. $mainPageLink );
-
-               $cdb = CargoUtils::getDB();
 
                // First, display a count.
                try {
@@ -137,12 +164,75 @@
                $out->addHTML( $text );
        }
 
+       function displayNumRowsForTable( $cdb, $tableName ) {
+               $res = $cdb->select( $tableName, 'COUNT(*) AS total' );
+               $row = $cdb->fetchRow( $res );
+               return $this->msg( 'cargo-cargotables-totalrowsshort' 
)->numParams( intval($row['total']) )->parse();
+       }
+
+       function displayActionLinksForTable( $tableName, $isReplacementTable, 
$canBeRecreated, $templateID ) {
+               global $wgUser;
+
+               $ctPage = SpecialPageFactory::getPage( 'CargoTables' );
+               $ctURL = $ctPage->getTitle()->getFullURL();
+               $viewURL = "$ctURL/$tableName";
+               if ( $isReplacementTable ) {
+                       $viewURL .= ( strpos( $viewURL, '?' ) ) ? '&' : '?';
+                       $viewURL .= "_replacement";
+               }
+               $actionLinks = Html::element( 'a', array( 'href' => $viewURL ),
+                               $this->msg( 'view' )->text() );
+
+               if ( method_exists( $this, 'getLinkRenderer' ) ) {
+                       $linkRenderer = $this->getLinkRenderer();
+               } else {
+                       $linkRenderer = null;
+               }
+
+               // Actions for this table - this display is modeled on
+               // Special:ListUsers.
+               $drilldownPage = SpecialPageFactory::getPage( 'Drilldown' );
+               $drilldownURL = $drilldownPage->getTitle()->getLocalURL() . '/' 
. $tableName;
+               $drilldownURL .= ( strpos( $drilldownURL, '?' ) ) ? '&' : '?';
+               if ( $isReplacementTable ) {
+                       $drilldownURL .= "_replacement";
+               } else {
+                       $drilldownURL .= "_single";
+               }
+               $actionLinks .= ' | ' . Html::element( 'a', array( 'href' => 
$drilldownURL ),
+                               $drilldownPage->getDescription() );
+
+               // It's a bit odd to include the "Recreate data" link, since
+               // it's an action for the template and not the table (if a
+               // template defines two tables, this will recreate both of
+               // them), but for standard setups, this makes things more
+               // convenient.
+               if ( $canBeRecreated && $wgUser->isAllowed( 'recreatecargodata' 
) ) {
+                       $templateTitle = Title::newFromID( $templateID );
+                       $actionLinks .= ' | ' . CargoUtils::makeLink( 
$linkRenderer, $templateTitle,
+                               $this->msg( 'recreatedata' )->text(), array(), 
array( 'action' => 'recreatedata' ) );
+               }
+
+               if ( $wgUser->isAllowed( 'deletecargodata' ) ) {
+                       $deleteTablePage = SpecialPageFactory::getPage( 
'DeleteCargoTable' );
+                       $deleteTableURL = 
$deleteTablePage->getTitle()->getLocalURL() . '/' . $tableName;
+                       $deleteTableURL .= ( strpos( $deleteTableURL, '?' ) ) ? 
'&' : '?';
+                       if ( $isReplacementTable ) {
+                               $deleteTableURL .= "_replacement";
+                       }
+                       $actionLinks .= ' | ' . Html::element( 'a', array( 
'href' => $deleteTableURL ),
+                                       $this->msg( 'delete' )->text() );
+               }
+
+               return $actionLinks;
+       }
+
        /**
         * Returns HTML for a bulleted list of Cargo tables, with various
         * links and information for each one.
         */
        function displayListOfTables() {
-               global $wgUser;
+               $this->getOutput()->addModules( 'ext.cargo.main' );
 
                $text = '';
 
@@ -168,8 +258,6 @@
                        $linkRenderer = null;
                }
 
-               $ctPage = SpecialPageFactory::getPage( 'CargoTables' );
-               $ctURL = $ctPage->getTitle()->getFullURL();
                $text .= Html::rawElement( 'p', null,
                        $this->msg( 'cargo-cargotables-tablelist' )->numParams( 
count( $tableNames ) )->parse() ) . "\n";
                $text .= "<ul>\n";
@@ -182,40 +270,17 @@
                                continue;
                        }
 
-                       $actionLinks = Html::element( 'a', array( 'href' => 
"$ctURL/$tableName", ),
-                                       $this->msg( 'view' )->text() );
-
-                       // Actions for this table - this display is modeled on
-                       // Special:ListUsers.
-                       $drilldownPage = SpecialPageFactory::getPage( 
'Drilldown' );
-                       $drilldownURL = 
$drilldownPage->getTitle()->getLocalURL() . '/' . $tableName;
-                       $drilldownURL .= ( strpos( $drilldownURL, '?' ) ) ? '&' 
: '?';
-                       $drilldownURL .= "_single";
-                       $actionLinks .= ' | ' . Html::element( 'a', array( 
'href' => $drilldownURL ),
-                                       $drilldownPage->getDescription() );
-
-                       // It's a bit odd to include the "Recreate data"
-                       // link, since it's an action for the template and
-                       // not the table (if a template defines two tables,
-                       // this will recreate both of them), but for standard
-                       // setups, this makes things more convenient.
-                       if ( $wgUser->isAllowed( 'recreatecargodata' ) && 
array_key_exists( $tableName, $templatesThatDeclareTables ) ) {
-                               $firstTemplateID = 
$templatesThatDeclareTables[$tableName][0];
-                               $templateTitle = Title::newFromID( 
$firstTemplateID );
-                               $actionLinks .= ' | ' . CargoUtils::makeLink( 
$linkRenderer, $templateTitle,
-                                       $this->msg( 'recreatedata' )->text(), 
array(), array( 'action' => 'recreatedata' ) );
+                       // Special handling for "replacement" tables.
+                       if ( substr( $tableName, -6 ) == '__NEXT' ) {
+                               continue;
                        }
+                       $hasReplacementTable = in_array( $tableName . '__NEXT', 
$tableNames );
 
-                       if ( $wgUser->isAllowed( 'deletecargodata' ) ) {
-                               $deleteTablePage = SpecialPageFactory::getPage( 
'DeleteCargoTable' );
-                               $deleteTableURL = 
$deleteTablePage->getTitle()->getLocalURL() . '/' . $tableName;
-                               $actionLinks .= ' | ' . Html::element( 'a', 
array( 'href' => $deleteTableURL ),
-                                               $this->msg( 'delete' )->text() 
);
-                       }
+                       $numRowsText = $this->displayNumRowsForTable( $cdb, 
$tableName );
 
-                       $res = $cdb->select( $tableName, 'COUNT(*) AS total' );
-                       $row = $cdb->fetchRow( $res );
-                       $numRowsText = $this->msg( 
'cargo-cargotables-totalrowsshort' )->numParams( intval($row['total']) 
)->parse();
+                       $canBeRecreated = !$hasReplacementTable && 
array_key_exists( $tableName, $templatesThatDeclareTables );
+                       $firstTemplateID = $canBeRecreated ? 
$templatesThatDeclareTables[$tableName][0] : null;
+                       $actionLinks = $this->displayActionLinksForTable( 
$tableName, false, $canBeRecreated, $firstTemplateID );
 
                        // "Declared by" text
                        if ( !array_key_exists( $tableName, 
$templatesThatDeclareTables ) ) {
@@ -256,6 +321,20 @@
                        }
                        $tableText .= ')';
 
+                       if ( $hasReplacementTable ) {
+                               global $wgUser;
+                               $numRowsText = $this->displayNumRowsForTable( 
$cdb, $tableName . '__NEXT' );
+                               $actionLinks = 
$this->displayActionLinksForTable( $tableName, true, false, null );
+                               $tableText .= "\n<div 
class=\"cargoReplacementTableInfo\">" . "A replacement table has been generated 
for this table ($actionLinks) - $numRowsText";
+                               if ( $wgUser->isAllowed( 'recreatecargodata' ) 
) {
+                                       $sctPage = SpecialPageFactory::getPage( 
'SwitchCargoTable' );
+                                       $switchURL = 
$sctPage->getTitle()->getFullURL() . "/$tableName";
+                                       $tableText .= "<br />\n" . 
Html::element( 'a', array( 'href' => $switchURL ),
+                                               $this->msg( 
"cargo-cargotables-switch" )->parse() );
+                               }
+                               $tableText .= "</div>";
+                       }
+
                        $text .= Html::rawElement( 'li', null, $tableText );
                }
                $text .= "</ul>\n";
@@ -265,4 +344,4 @@
        protected function getGroupName() {
                return 'cargo';
        }
-}
+}
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/386635
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I1ff6c5aeb5524c963126a3b622c6ff5c2c1d3330
Gerrit-PatchSet: 2
Gerrit-Project: mediawiki/extensions/Cargo
Gerrit-Branch: master
Gerrit-Owner: Yaron Koren <yaro...@gmail.com>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to