MediaWiki REL1_34
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 master 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', $this->getOption( 'slave', '' ) );
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_MASTER;
84 }
85
86 $db = $lb->getMaintenanceConnectionRef( $index, [], $wiki );
87 if ( $replicaDB != '' && $db->getLBInfo( 'master' ) !== null ) {
88 $this->fatalError( "The server selected ({$db->getServer()}) is not a replica DB." );
89 }
90
91 if ( $index === DB_MASTER ) {
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' ] );
103 if ( $error !== true ) {
104 $this->fatalError( $error );
105 } else {
106 exit( 0 );
107 }
108 }
109
110 if ( $this->hasOption( 'query' ) ) {
111 $query = $this->getOption( 'query' );
112 $res = $this->sqlDoQuery( $db, $query, /* dieOnError */ true );
114 if ( $this->hasOption( 'status' ) ) {
115 exit( $res ? 0 : 2 );
116 }
117 return;
118 }
119
120 if (
121 function_exists( 'readline_add_history' ) &&
122 Maintenance::posix_isatty( 0 /*STDIN*/ )
123 ) {
124 $historyFile = isset( $_ENV['HOME'] ) ?
125 "{$_ENV['HOME']}/.mwsql_history" : "$IP/maintenance/.mwsql_history";
126 readline_read_history( $historyFile );
127 } else {
128 $historyFile = null;
129 }
130
131 $wholeLine = '';
132 $newPrompt = '> ';
133 $prompt = $newPrompt;
134 $doDie = !Maintenance::posix_isatty( 0 );
135 $res = 1;
136 while ( ( $line = Maintenance::readconsole( $prompt ) ) !== false ) {
137 if ( !$line ) {
138 # User simply pressed return key
139 continue;
140 }
141 $done = $db->streamStatementEnd( $wholeLine, $line );
142
143 $wholeLine .= $line;
144
145 if ( !$done ) {
146 $wholeLine .= ' ';
147 $prompt = ' -> ';
148 continue;
149 }
150 if ( $historyFile ) {
151 # Delimiter is eated by streamStatementEnd, we add it
152 # up in the history (T39020)
153 readline_add_history( $wholeLine . ';' );
154 readline_write_history( $historyFile );
155 }
156 $res = $this->sqlDoQuery( $db, $wholeLine, $doDie );
157 $prompt = $newPrompt;
158 $wholeLine = '';
159 }
161 if ( $this->hasOption( 'status' ) ) {
162 exit( $res ? 0 : 2 );
163 }
164 }
165
172 protected function sqlDoQuery( IDatabase $db, $line, $dieOnError ) {
173 try {
174 $res = $db->query( $line );
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;
wfWaitForSlaves( $ifWritesSince=null, $wiki=false, $cluster=false, $timeout=null)
Waits for the replica DBs to catch up to the master position.
const RUN_MAINTENANCE_IF_MAIN
$IP
Definition WebStart.php:41
$line
Definition cdb.php:59
static newForDB(IMaintainableDatabase $db, $shared=false, Maintenance $maintenance=null)
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 exists.
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.
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:26
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