sql.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <?php
  2. /**
  3. * Send SQL queries from the specified file to the database, performing
  4. * variable replacement along the way.
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program; if not, write to the Free Software Foundation, Inc.,
  18. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. * http://www.gnu.org/copyleft/gpl.html
  20. *
  21. * @file
  22. * @ingroup Maintenance
  23. */
  24. require_once __DIR__ . '/Maintenance.php';
  25. /**
  26. * Maintenance script that sends SQL queries from the specified file to the database.
  27. *
  28. * @ingroup Maintenance
  29. */
  30. class MwSql extends Maintenance {
  31. public function __construct() {
  32. parent::__construct();
  33. $this->addDescription( 'Send SQL queries to a MediaWiki database. ' .
  34. 'Takes a file name containing SQL as argument or runs interactively.' );
  35. $this->addOption( 'query', 'Run a single query instead of running interactively', false, true );
  36. $this->addOption( 'cluster', 'Use an external cluster by name', false, true );
  37. $this->addOption( 'wikidb', 'The database wiki ID to use if not the current one', false, true );
  38. $this->addOption( 'slave', 'Use a slave server (either "any" or by name)', false, true );
  39. }
  40. public function execute() {
  41. // We wan't to allow "" for the wikidb, meaning don't call select_db()
  42. $wiki = $this->hasOption( 'wikidb' ) ? $this->getOption( 'wikidb' ) : false;
  43. // Get the appropriate load balancer (for this wiki)
  44. if ( $this->hasOption( 'cluster' ) ) {
  45. $lb = wfGetLBFactory()->getExternalLB( $this->getOption( 'cluster' ), $wiki );
  46. } else {
  47. $lb = wfGetLB( $wiki );
  48. }
  49. // Figure out which server to use
  50. if ( $this->hasOption( 'slave' ) ) {
  51. $server = $this->getOption( 'slave' );
  52. if ( $server === 'any' ) {
  53. $index = DB_SLAVE;
  54. } else {
  55. $index = null;
  56. $serverCount = $lb->getServerCount();
  57. for ( $i = 0; $i < $serverCount; ++$i ) {
  58. if ( $lb->getServerName( $i ) === $server ) {
  59. $index = $i;
  60. break;
  61. }
  62. }
  63. if ( $index === null ) {
  64. $this->error( "No slave server configured with the name '$server'.", 1 );
  65. }
  66. }
  67. } else {
  68. $index = DB_MASTER;
  69. }
  70. // Get a DB handle (with this wiki's DB selected) from the appropriate load balancer
  71. $db = $lb->getConnection( $index, [], $wiki );
  72. if ( $this->hasOption( 'slave' ) && $db->getLBInfo( 'master' ) !== null ) {
  73. $this->error( "The server selected ({$db->getServer()}) is not a slave.", 1 );
  74. }
  75. if ( $this->hasArg( 0 ) ) {
  76. $file = fopen( $this->getArg( 0 ), 'r' );
  77. if ( !$file ) {
  78. $this->error( "Unable to open input file", true );
  79. }
  80. $error = $db->sourceStream( $file, false, [ $this, 'sqlPrintResult' ] );
  81. if ( $error !== true ) {
  82. $this->error( $error, true );
  83. } else {
  84. exit( 0 );
  85. }
  86. }
  87. if ( $this->hasOption( 'query' ) ) {
  88. $query = $this->getOption( 'query' );
  89. $this->sqlDoQuery( $db, $query, /* dieOnError */ true );
  90. wfWaitForSlaves();
  91. return;
  92. }
  93. $useReadline = function_exists( 'readline_add_history' )
  94. && Maintenance::posix_isatty( 0 /*STDIN*/ );
  95. if ( $useReadline ) {
  96. global $IP;
  97. $historyFile = isset( $_ENV['HOME'] ) ?
  98. "{$_ENV['HOME']}/.mwsql_history" : "$IP/maintenance/.mwsql_history";
  99. readline_read_history( $historyFile );
  100. }
  101. $wholeLine = '';
  102. $newPrompt = '> ';
  103. $prompt = $newPrompt;
  104. $doDie = !Maintenance::posix_isatty( 0 );
  105. while ( ( $line = Maintenance::readconsole( $prompt ) ) !== false ) {
  106. if ( !$line ) {
  107. # User simply pressed return key
  108. continue;
  109. }
  110. $done = $db->streamStatementEnd( $wholeLine, $line );
  111. $wholeLine .= $line;
  112. if ( !$done ) {
  113. $wholeLine .= ' ';
  114. $prompt = ' -> ';
  115. continue;
  116. }
  117. if ( $useReadline ) {
  118. # Delimiter is eated by streamStatementEnd, we add it
  119. # up in the history (bug 37020)
  120. readline_add_history( $wholeLine . $db->getDelimiter() );
  121. readline_write_history( $historyFile );
  122. }
  123. $this->sqlDoQuery( $db, $wholeLine, $doDie );
  124. $prompt = $newPrompt;
  125. $wholeLine = '';
  126. }
  127. wfWaitForSlaves();
  128. }
  129. protected function sqlDoQuery( $db, $line, $dieOnError ) {
  130. try {
  131. $res = $db->query( $line );
  132. $this->sqlPrintResult( $res, $db );
  133. } catch ( DBQueryError $e ) {
  134. $this->error( $e, $dieOnError );
  135. }
  136. }
  137. /**
  138. * Print the results, callback for $db->sourceStream()
  139. * @param ResultWrapper $res The results object
  140. * @param DatabaseBase $db
  141. */
  142. public function sqlPrintResult( $res, $db ) {
  143. if ( !$res ) {
  144. // Do nothing
  145. return;
  146. } elseif ( is_object( $res ) && $res->numRows() ) {
  147. foreach ( $res as $row ) {
  148. $this->output( print_r( $row, true ) );
  149. }
  150. } else {
  151. $affected = $db->affectedRows();
  152. $this->output( "Query OK, $affected row(s) affected\n" );
  153. }
  154. }
  155. /**
  156. * @return int DB_TYPE constant
  157. */
  158. public function getDbType() {
  159. return Maintenance::DB_ADMIN;
  160. }
  161. }
  162. $maintClass = "MwSql";
  163. require_once RUN_MAINTENANCE_IF_MAIN;