X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/maintenance/sql.php diff --git a/maintenance/sql.php b/maintenance/sql.php index e1f2bb5f..36e55f3e 100644 --- a/maintenance/sql.php +++ b/maintenance/sql.php @@ -18,48 +18,158 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname( __FILE__ ) . '/Maintenance.php' ); +require_once __DIR__ . '/Maintenance.php'; +use Wikimedia\Rdbms\ResultWrapper; +use Wikimedia\Rdbms\IDatabase; +use Wikimedia\Rdbms\DBQueryError; + +/** + * Maintenance script that sends SQL queries from the specified file to the database. + * + * @ingroup Maintenance + */ class MwSql extends Maintenance { public function __construct() { parent::__construct(); - $this->mDescription = "Send SQL queries to a MediaWiki database"; + $this->addDescription( 'Send SQL queries to a MediaWiki database. ' . + 'Takes a file name containing SQL as argument or runs interactively.' ); + $this->addOption( 'query', + 'Run a single query instead of running interactively', false, true ); + $this->addOption( 'cluster', 'Use an external cluster by name', false, true ); + $this->addOption( 'wikidb', + 'The database wiki ID to use if not the current one', false, true ); + $this->addOption( 'replicadb', + 'Replica DB server to use instead of the master DB (can be "any")', false, true ); } public function execute() { - if ( $this->hasArg() ) { - $fileName = $this->getArg(); - $file = fopen( $fileName, 'r' ); - $promptCallback = false; + global $IP; + + // We wan't to allow "" for the wikidb, meaning don't call select_db() + $wiki = $this->hasOption( 'wikidb' ) ? $this->getOption( 'wikidb' ) : false; + // Get the appropriate load balancer (for this wiki) + if ( $this->hasOption( 'cluster' ) ) { + $lb = wfGetLBFactory()->getExternalLB( $this->getOption( 'cluster' ) ); + } else { + $lb = wfGetLB( $wiki ); + } + // Figure out which server to use + $replicaDB = $this->getOption( 'replicadb', $this->getOption( 'slave', '' ) ); + if ( $replicaDB === 'any' ) { + $index = DB_REPLICA; + } elseif ( $replicaDB != '' ) { + $index = null; + $serverCount = $lb->getServerCount(); + for ( $i = 0; $i < $serverCount; ++$i ) { + if ( $lb->getServerName( $i ) === $replicaDB ) { + $index = $i; + break; + } + } + if ( $index === null ) { + $this->error( "No replica DB server configured with the name '$replicaDB'.", 1 ); + } } else { - $file = $this->getStdin(); - $promptObject = new SqlPromptPrinter( "> " ); - $promptCallback = $promptObject->cb(); + $index = DB_MASTER; } - if ( !$file ) - $this->error( "Unable to open input file", true ); + /** @var IDatabase $db DB handle for the appropriate cluster/wiki */ + $db = $lb->getConnection( $index, [], $wiki ); + if ( $replicaDB != '' && $db->getLBInfo( 'master' ) !== null ) { + $this->error( "The server selected ({$db->getServer()}) is not a replica DB.", 1 ); + } + + if ( $index === DB_MASTER ) { + $updater = DatabaseUpdater::newForDB( $db, true, $this ); + $db->setSchemaVars( $updater->getSchemaVars() ); + } + + if ( $this->hasArg( 0 ) ) { + $file = fopen( $this->getArg( 0 ), 'r' ); + if ( !$file ) { + $this->error( "Unable to open input file", true ); + } + + $error = $db->sourceStream( $file, null, [ $this, 'sqlPrintResult' ] ); + if ( $error !== true ) { + $this->error( $error, true ); + } else { + exit( 0 ); + } + } + + if ( $this->hasOption( 'query' ) ) { + $query = $this->getOption( 'query' ); + $this->sqlDoQuery( $db, $query, /* dieOnError */ true ); + wfWaitForSlaves(); + return; + } - $dbw = wfGetDB( DB_MASTER ); - $error = $dbw->sourceStream( $file, $promptCallback, array( $this, 'sqlPrintResult' ) ); - if ( $error !== true ) { - $this->error( $error, true ); + if ( + function_exists( 'readline_add_history' ) && + Maintenance::posix_isatty( 0 /*STDIN*/ ) + ) { + $historyFile = isset( $_ENV['HOME'] ) ? + "{$_ENV['HOME']}/.mwsql_history" : "$IP/maintenance/.mwsql_history"; + readline_read_history( $historyFile ); } else { - exit( 0 ); + $historyFile = null; + } + + $wholeLine = ''; + $newPrompt = '> '; + $prompt = $newPrompt; + $doDie = !Maintenance::posix_isatty( 0 ); + while ( ( $line = Maintenance::readconsole( $prompt ) ) !== false ) { + if ( !$line ) { + # User simply pressed return key + continue; + } + $done = $db->streamStatementEnd( $wholeLine, $line ); + + $wholeLine .= $line; + + if ( !$done ) { + $wholeLine .= ' '; + $prompt = ' -> '; + continue; + } + if ( $historyFile ) { + # Delimiter is eated by streamStatementEnd, we add it + # up in the history (T39020) + readline_add_history( $wholeLine . ';' ); + readline_write_history( $historyFile ); + } + $this->sqlDoQuery( $db, $wholeLine, $doDie ); + $prompt = $newPrompt; + $wholeLine = ''; + } + wfWaitForSlaves(); + } + + protected function sqlDoQuery( IDatabase $db, $line, $dieOnError ) { + try { + $res = $db->query( $line ); + $this->sqlPrintResult( $res, $db ); + } catch ( DBQueryError $e ) { + $this->error( $e, $dieOnError ); } } /** * Print the results, callback for $db->sourceStream() - * @param $res The results object - * @param $db Database object + * @param ResultWrapper|bool $res The results object + * @param IDatabase $db */ public function sqlPrintResult( $res, $db ) { if ( !$res ) { // Do nothing + return; } elseif ( is_object( $res ) && $res->numRows() ) { foreach ( $res as $row ) { $this->output( print_r( $row, true ) ); @@ -70,24 +180,13 @@ class MwSql extends Maintenance { } } + /** + * @return int DB_TYPE constant + */ public function getDbType() { return Maintenance::DB_ADMIN; } } -class SqlPromptPrinter { - function __construct( $prompt ) { - $this->prompt = $prompt; - } - - function cb() { - return array( $this, 'printPrompt' ); - } - - function printPrompt() { - echo $this->prompt; - } -} - $maintClass = "MwSql"; -require_once( RUN_MAINTENANCE_IF_MAIN ); +require_once RUN_MAINTENANCE_IF_MAIN;