MediaWiki REL1_37
sql.php
Go to the documentation of this file.
1<?php
25require_once __DIR__ . '/Maintenance.php';
26
31
37class MwSql extends Maintenance {
38 public function __construct() {
39 parent::__construct();
40 $this->addDescription( 'Send SQL queries to a MediaWiki database. ' .
41 'Takes a file name containing SQL as argument or runs interactively.' );
42 $this->addOption( 'query',
43 'Run a single query instead of running interactively', false, true );
44 $this->addOption( 'json', 'Output the results as JSON instead of PHP objects' );
45 $this->addOption( 'status', 'Return successful exit status only if the query succeeded '
46 . '(selected or altered rows), otherwise 1 for errors, 2 for no rows' );
47 $this->addOption( 'cluster', 'Use an external cluster by name', false, true );
48 $this->addOption( 'wikidb',
49 'The database wiki ID to use if not the current one', false, true );
50 $this->addOption( 'replicadb',
51 'Replica DB server to use instead of the primary DB (can be "any")', false, true );
52 }
53
54 public function execute() {
55 global $IP;
56
57 // We wan't to allow "" for the wikidb, meaning don't call select_db()
58 $wiki = $this->hasOption( 'wikidb' ) ? $this->getOption( 'wikidb' ) : false;
59 // Get the appropriate load balancer (for this wiki)
60 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
61 if ( $this->hasOption( 'cluster' ) ) {
62 $lb = $lbFactory->getExternalLB( $this->getOption( 'cluster' ) );
63 } else {
64 $lb = $lbFactory->getMainLB( $wiki );
65 }
66 // Figure out which server to use
67 $replicaDB = $this->getOption( 'replicadb', '' );
68 if ( $replicaDB === 'any' ) {
69 $index = DB_REPLICA;
70 } elseif ( $replicaDB !== '' ) {
71 $index = null;
72 $serverCount = $lb->getServerCount();
73 for ( $i = 0; $i < $serverCount; ++$i ) {
74 if ( $lb->getServerName( $i ) === $replicaDB ) {
75 $index = $i;
76 break;
77 }
78 }
79 if ( $index === null || $index === $lb->getWriterIndex() ) {
80 $this->fatalError( "No replica DB server configured with the name '$replicaDB'." );
81 }
82 } else {
83 $index = DB_PRIMARY;
84 }
85
86 $db = $lb->getMaintenanceConnectionRef( $index, [], $wiki );
87 if ( $replicaDB != '' && $db->getLBInfo( 'master' ) !== null ) {
88 $this->fatalError( "Server {$db->getServerName()} is not a replica DB." );
89 }
90
91 if ( $index === DB_PRIMARY ) {
92 $updater = DatabaseUpdater::newForDB( $db, true, $this );
93 $db->setSchemaVars( $updater->getSchemaVars() );
94 }
95
96 if ( $this->hasArg( 0 ) ) {
97 $file = fopen( $this->getArg( 0 ), 'r' );
98 if ( !$file ) {
99 $this->fatalError( "Unable to open input file" );
100 }
101
102 $error = $db->sourceStream( $file, null, [ $this, 'sqlPrintResult' ], __METHOD__ );
103 if ( $error !== true ) {
104 $this->fatalError( $error );
105 }
106 return;
107 }
108
109 if ( $this->hasOption( 'query' ) ) {
110 $query = $this->getOption( 'query' );
111 $res = $this->sqlDoQuery( $db, $query, /* dieOnError */ true );
112 $lbFactory->waitForReplication();
113 if ( $this->hasOption( 'status' ) && !$res ) {
114 $this->fatalError( 'Failed.', 2 );
115 }
116 return;
117 }
118
119 if (
120 function_exists( 'readline_add_history' ) &&
121 Maintenance::posix_isatty( 0 /*STDIN*/ )
122 ) {
123 $historyFile = isset( $_ENV['HOME'] ) ?
124 "{$_ENV['HOME']}/.mwsql_history" : "$IP/maintenance/.mwsql_history";
125 readline_read_history( $historyFile );
126 } else {
127 $historyFile = null;
128 }
129
130 $wholeLine = '';
131 $newPrompt = '> ';
132 $prompt = $newPrompt;
133 $doDie = !Maintenance::posix_isatty( 0 );
134 $res = 1;
135 while ( ( $line = Maintenance::readconsole( $prompt ) ) !== false ) {
136 if ( !$line ) {
137 # User simply pressed return key
138 continue;
139 }
140 $done = $db->streamStatementEnd( $wholeLine, $line );
141
142 $wholeLine .= $line;
143
144 if ( !$done ) {
145 $wholeLine .= ' ';
146 $prompt = ' -> ';
147 continue;
148 }
149 if ( $historyFile ) {
150 # Delimiter is eated by streamStatementEnd, we add it
151 # up in the history (T39020)
152 readline_add_history( $wholeLine . ';' );
153 readline_write_history( $historyFile );
154 }
155 // @phan-suppress-next-line SecurityCheck-SQLInjection
156 $res = $this->sqlDoQuery( $db, $wholeLine, $doDie );
157 $prompt = $newPrompt;
158 $wholeLine = '';
159 }
160 $lbFactory->waitForReplication();
161 if ( $this->hasOption( 'status' ) && !$res ) {
162 $this->fatalError( 'Failed.', 2 );
163 }
164 }
165
172 protected function sqlDoQuery( IDatabase $db, $line, $dieOnError ) {
173 try {
174 $res = $db->query( $line, __METHOD__ );
175 return $this->sqlPrintResult( $res, $db );
176 } catch ( DBQueryError $e ) {
177 if ( $dieOnError ) {
178 $this->fatalError( (string)$e );
179 } else {
180 $this->error( (string)$e );
181 }
182 }
183 return null;
184 }
185
192 public function sqlPrintResult( $res, $db ) {
193 if ( !$res ) {
194 // Do nothing
195 return null;
196 } elseif ( is_object( $res ) ) {
197 $out = '';
198 $rows = [];
199 foreach ( $res as $row ) {
200 $out .= print_r( $row, true );
201 $rows[] = $row;
202 }
203 if ( $this->hasOption( 'json' ) ) {
204 $out = json_encode( $rows, JSON_PRETTY_PRINT );
205 } elseif ( !$rows ) {
206 $out = 'Query OK, 0 row(s) affected';
207 }
208 $this->output( $out . "\n" );
209 return count( $rows );
210 } else {
211 $affected = $db->affectedRows();
212 if ( $this->hasOption( 'json' ) ) {
213 $this->output( json_encode( [ 'affected' => $affected ], JSON_PRETTY_PRINT ) . "\n" );
214 } else {
215 $this->output( "Query OK, $affected row(s) affected\n" );
216 }
217 return $affected;
218 }
219 }
220
224 public function getDbType() {
226 }
227}
228
229$maintClass = MwSql::class;
230require_once RUN_MAINTENANCE_IF_MAIN;
$IP
Definition WebStart.php:49
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
error( $err, $die=0)
Throw an error to the user.
output( $out, $channel=null)
Throw some output to the user.
hasArg( $argId=0)
Does a given argument exist?
hasOption( $name)
Checks to see if a particular option was set.
static readconsole( $prompt='> ')
Prompt the console for input.
static posix_isatty( $fd)
Wrapper for posix_isatty() We default as considering stdin a tty (for nice readline methods) but trea...
getArg( $argId=0, $default=null)
Get an argument.
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Maintenance script that sends SQL queries from the specified file to the database.
Definition sql.php:37
sqlPrintResult( $res, $db)
Print the results, callback for $db->sourceStream()
Definition sql.php:192
__construct()
Default constructor.
Definition sql.php:38
sqlDoQuery(IDatabase $db, $line, $dieOnError)
Definition sql.php:172
execute()
Do the actual work.
Definition sql.php:54
getDbType()
Definition sql.php:224
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query and return the result.
Result wrapper for grabbing data queried from an IDatabase object.
$line
Definition mcc.php:119
const DB_REPLICA
Definition defines.php:25
const DB_PRIMARY
Definition defines.php:27
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
$maintClass
Definition sql.php:229