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