MediaWiki REL1_28
Maintenance.php
Go to the documentation of this file.
1<?php
23// Bail on old versions of PHP, or if composer has not been run yet to install
24// dependencies.
25require_once __DIR__ . '/../includes/PHPVersionCheck.php';
26wfEntryPointCheck( 'cli' );
27
33// Define this so scripts can easily find doMaintenance.php
34define( 'RUN_MAINTENANCE_IF_MAIN', __DIR__ . '/doMaintenance.php' );
35define( 'DO_MAINTENANCE', RUN_MAINTENANCE_IF_MAIN ); // original name, harmless
36
38
41
52abstract class Maintenance {
57 const DB_NONE = 0;
58 const DB_STD = 1;
59 const DB_ADMIN = 2;
60
61 // Const for getStdin()
62 const STDIN_ALL = 'all';
63
64 // This is the desired params
65 protected $mParams = [];
66
67 // Array of mapping short parameters to long ones
68 protected $mShortParamsMap = [];
69
70 // Array of desired args
71 protected $mArgList = [];
72
73 // This is the list of options that were actually passed
74 protected $mOptions = [];
75
76 // This is the list of arguments that were actually passed
77 protected $mArgs = [];
78
79 // Name of the script currently running
80 protected $mSelf;
81
82 // Special vars for params that are always used
83 protected $mQuiet = false;
84 protected $mDbUser, $mDbPass;
85
86 // A description of the script, children should change this via addDescription()
87 protected $mDescription = '';
88
89 // Have we already loaded our user input?
90 protected $mInputLoaded = false;
91
98 protected $mBatchSize = null;
99
100 // Generic options added by addDefaultParams()
102 // Generic options which might or not be supported by the script
104
109 private $mDb = null;
110
112 private $lastReplicationWait = 0.0;
113
119
125 private $config;
126
132
144 public $orderedOptions = [];
145
150 public function __construct() {
151 // Setup $IP, using MW_INSTALL_PATH if it exists
152 global $IP;
153 $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== ''
154 ? getenv( 'MW_INSTALL_PATH' )
155 : realpath( __DIR__ . '/..' );
156
157 $this->addDefaultParams();
158 register_shutdown_function( [ $this, 'outputChanneled' ], false );
159 }
160
168 public static function shouldExecute() {
170
171 if ( !function_exists( 'debug_backtrace' ) ) {
172 // If someone has a better idea...
173 return $wgCommandLineMode;
174 }
175
176 $bt = debug_backtrace();
177 $count = count( $bt );
178 if ( $count < 2 ) {
179 return false; // sanity
180 }
181 if ( $bt[0]['class'] !== 'Maintenance' || $bt[0]['function'] !== 'shouldExecute' ) {
182 return false; // last call should be to this function
183 }
184 $includeFuncs = [ 'require_once', 'require', 'include', 'include_once' ];
185 for ( $i = 1; $i < $count; $i++ ) {
186 if ( !in_array( $bt[$i]['function'], $includeFuncs ) ) {
187 return false; // previous calls should all be "requires"
188 }
189 }
190
191 return true;
192 }
193
197 abstract public function execute();
198
210 protected function addOption( $name, $description, $required = false,
211 $withArg = false, $shortName = false, $multiOccurrence = false
212 ) {
213 $this->mParams[$name] = [
214 'desc' => $description,
215 'require' => $required,
216 'withArg' => $withArg,
217 'shortName' => $shortName,
218 'multiOccurrence' => $multiOccurrence
219 ];
220
221 if ( $shortName !== false ) {
222 $this->mShortParamsMap[$shortName] = $name;
223 }
224 }
225
231 protected function hasOption( $name ) {
232 return isset( $this->mOptions[$name] );
233 }
234
245 protected function getOption( $name, $default = null ) {
246 if ( $this->hasOption( $name ) ) {
247 return $this->mOptions[$name];
248 } else {
249 // Set it so we don't have to provide the default again
250 $this->mOptions[$name] = $default;
251
252 return $this->mOptions[$name];
253 }
254 }
255
262 protected function addArg( $arg, $description, $required = true ) {
263 $this->mArgList[] = [
264 'name' => $arg,
265 'desc' => $description,
266 'require' => $required
267 ];
268 }
269
274 protected function deleteOption( $name ) {
275 unset( $this->mParams[$name] );
276 }
277
282 protected function addDescription( $text ) {
283 $this->mDescription = $text;
284 }
285
291 protected function hasArg( $argId = 0 ) {
292 return isset( $this->mArgs[$argId] );
293 }
294
301 protected function getArg( $argId = 0, $default = null ) {
302 return $this->hasArg( $argId ) ? $this->mArgs[$argId] : $default;
303 }
304
309 protected function setBatchSize( $s = 0 ) {
310 $this->mBatchSize = $s;
311
312 // If we support $mBatchSize, show the option.
313 // Used to be in addDefaultParams, but in order for that to
314 // work, subclasses would have to call this function in the constructor
315 // before they called parent::__construct which is just weird
316 // (and really wasn't done).
317 if ( $this->mBatchSize ) {
318 $this->addOption( 'batch-size', 'Run this many operations ' .
319 'per batch, default: ' . $this->mBatchSize, false, true );
320 if ( isset( $this->mParams['batch-size'] ) ) {
321 // This seems a little ugly...
322 $this->mDependantParameters['batch-size'] = $this->mParams['batch-size'];
323 }
324 }
325 }
326
331 public function getName() {
332 return $this->mSelf;
333 }
334
341 protected function getStdin( $len = null ) {
342 if ( $len == Maintenance::STDIN_ALL ) {
343 return file_get_contents( 'php://stdin' );
344 }
345 $f = fopen( 'php://stdin', 'rt' );
346 if ( !$len ) {
347 return $f;
348 }
349 $input = fgets( $f, $len );
350 fclose( $f );
351
352 return rtrim( $input );
353 }
354
358 public function isQuiet() {
359 return $this->mQuiet;
360 }
361
368 protected function output( $out, $channel = null ) {
369 if ( $this->mQuiet ) {
370 return;
371 }
372 if ( $channel === null ) {
373 $this->cleanupChanneled();
374 print $out;
375 } else {
376 $out = preg_replace( '/\n\z/', '', $out );
377 $this->outputChanneled( $out, $channel );
378 }
379 }
380
387 protected function error( $err, $die = 0 ) {
388 $this->outputChanneled( false );
389 if ( PHP_SAPI == 'cli' ) {
390 fwrite( STDERR, $err . "\n" );
391 } else {
392 print $err;
393 }
394 $die = intval( $die );
395 if ( $die > 0 ) {
396 die( $die );
397 }
398 }
399
400 private $atLineStart = true;
401 private $lastChannel = null;
402
406 public function cleanupChanneled() {
407 if ( !$this->atLineStart ) {
408 print "\n";
409 $this->atLineStart = true;
410 }
411 }
412
421 public function outputChanneled( $msg, $channel = null ) {
422 if ( $msg === false ) {
423 $this->cleanupChanneled();
424
425 return;
426 }
427
428 // End the current line if necessary
429 if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
430 print "\n";
431 }
432
433 print $msg;
434
435 $this->atLineStart = false;
436 if ( $channel === null ) {
437 // For unchanneled messages, output trailing newline immediately
438 print "\n";
439 $this->atLineStart = true;
440 }
441 $this->lastChannel = $channel;
442 }
443
454 public function getDbType() {
455 return Maintenance::DB_STD;
456 }
457
461 protected function addDefaultParams() {
462
463 # Generic (non script dependant) options:
464
465 $this->addOption( 'help', 'Display this help message', false, false, 'h' );
466 $this->addOption( 'quiet', 'Whether to supress non-error output', false, false, 'q' );
467 $this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true );
468 $this->addOption( 'wiki', 'For specifying the wiki ID', false, true );
469 $this->addOption( 'globals', 'Output globals at the end of processing for debugging' );
470 $this->addOption(
471 'memory-limit',
472 'Set a specific memory limit for the script, '
473 . '"max" for no limit or "default" to avoid changing it'
474 );
475 $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " .
476 "http://en.wikipedia.org. This is sometimes necessary because " .
477 "server name detection may fail in command line scripts.", false, true );
478 $this->addOption( 'profiler', 'Profiler output format (usually "text")', false, true );
479
480 # Save generic options to display them separately in help
481 $this->mGenericParameters = $this->mParams;
482
483 # Script dependant options:
484
485 // If we support a DB, show the options
486 if ( $this->getDbType() > 0 ) {
487 $this->addOption( 'dbuser', 'The DB user to use for this script', false, true );
488 $this->addOption( 'dbpass', 'The password to use for this script', false, true );
489 }
490
491 # Save additional script dependant options to display
492 #  them separately in help
493 $this->mDependantParameters = array_diff_key( $this->mParams, $this->mGenericParameters );
494 }
495
500 public function getConfig() {
501 if ( $this->config === null ) {
502 $this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
503 }
504
505 return $this->config;
506 }
507
512 public function setConfig( Config $config ) {
513 $this->config = $config;
514 }
515
525 protected function requireExtension( $name ) {
526 $this->requiredExtensions[] = $name;
527 }
528
534 public function checkRequiredExtensions() {
535 $registry = ExtensionRegistry::getInstance();
536 $missing = [];
537 foreach ( $this->requiredExtensions as $name ) {
538 if ( !$registry->isLoaded( $name ) ) {
539 $missing[] = $name;
540 }
541 }
542
543 if ( $missing ) {
544 $joined = implode( ', ', $missing );
545 $msg = "The following extensions are required to be installed "
546 . "for this script to run: $joined. Please enable them and then try again.";
547 $this->error( $msg, 1 );
548 }
549
550 }
551
556 public function setAgentAndTriggers() {
557 if ( function_exists( 'posix_getpwuid' ) ) {
558 $agent = posix_getpwuid( posix_geteuid() )['name'];
559 } else {
560 $agent = 'sysadmin';
561 }
562 $agent .= '@' . wfHostname();
563
564 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
565 // Add a comment for easy SHOW PROCESSLIST interpretation
566 $lbFactory->setAgentName(
567 mb_strlen( $agent ) > 15 ? mb_substr( $agent, 0, 15 ) . '...' : $agent
568 );
570 }
571
576 public static function setLBFactoryTriggers( LBFactory $LBFactory ) {
577 // Hook into period lag checks which often happen in long-running scripts
578 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
579 $lbFactory->setWaitForReplicationListener(
580 __METHOD__,
581 function () {
583 // Check config in case of JobRunner and unit tests
584 if ( $wgCommandLineMode ) {
585 DeferredUpdates::tryOpportunisticExecute( 'run' );
586 }
587 }
588 );
589 // Check for other windows to run them. A script may read or do a few writes
590 // to the master but mostly be writing to something else, like a file store.
591 $lbFactory->getMainLB()->setTransactionListener(
592 __METHOD__,
593 function ( $trigger ) {
595 // Check config in case of JobRunner and unit tests
596 if ( $wgCommandLineMode && $trigger === IDatabase::TRIGGER_COMMIT ) {
597 DeferredUpdates::tryOpportunisticExecute( 'run' );
598 }
599 }
600 );
601 }
602
610 public function runChild( $maintClass, $classFile = null ) {
611 // Make sure the class is loaded first
612 if ( !class_exists( $maintClass ) ) {
613 if ( $classFile ) {
614 require_once $classFile;
615 }
616 if ( !class_exists( $maintClass ) ) {
617 $this->error( "Cannot spawn child: $maintClass" );
618 }
619 }
620
624 $child = new $maintClass();
625 $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs );
626 if ( !is_null( $this->mDb ) ) {
627 $child->setDB( $this->mDb );
628 }
629
630 return $child;
631 }
632
636 public function setup() {
638
639 # Abort if called from a web server
640 if ( isset( $_SERVER ) && isset( $_SERVER['REQUEST_METHOD'] ) ) {
641 $this->error( 'This script must be run from the command line', true );
642 }
643
644 if ( $IP === null ) {
645 $this->error( "\$IP not set, aborting!\n" .
646 '(Did you forget to call parent::__construct() in your maintenance script?)', 1 );
647 }
648
649 # Make sure we can handle script parameters
650 if ( !defined( 'HPHP_VERSION' ) && !ini_get( 'register_argc_argv' ) ) {
651 $this->error( 'Cannot get command line arguments, register_argc_argv is set to false', true );
652 }
653
654 // Send PHP warnings and errors to stderr instead of stdout.
655 // This aids in diagnosing problems, while keeping messages
656 // out of redirected output.
657 if ( ini_get( 'display_errors' ) ) {
658 ini_set( 'display_errors', 'stderr' );
659 }
660
661 $this->loadParamsAndArgs();
662 $this->maybeHelp();
663
664 # Set the memory limit
665 # Note we need to set it again later in cache LocalSettings changed it
666 $this->adjustMemoryLimit();
667
668 # Set max execution time to 0 (no limit). PHP.net says that
669 # "When running PHP from the command line the default setting is 0."
670 # But sometimes this doesn't seem to be the case.
671 ini_set( 'max_execution_time', 0 );
672
673 $wgRequestTime = microtime( true );
674
675 # Define us as being in MediaWiki
676 define( 'MEDIAWIKI', true );
677
678 $wgCommandLineMode = true;
679
680 # Turn off output buffering if it's on
681 while ( ob_get_level() > 0 ) {
682 ob_end_flush();
683 }
684
685 $this->validateParamsAndArgs();
686 }
687
697 public function memoryLimit() {
698 $limit = $this->getOption( 'memory-limit', 'max' );
699 $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood
700 return $limit;
701 }
702
706 protected function adjustMemoryLimit() {
707 $limit = $this->memoryLimit();
708 if ( $limit == 'max' ) {
709 $limit = -1; // no memory limit
710 }
711 if ( $limit != 'default' ) {
712 ini_set( 'memory_limit', $limit );
713 }
714 }
715
719 protected function activateProfiler() {
721
722 $output = $this->getOption( 'profiler' );
723 if ( !$output ) {
724 return;
725 }
726
727 if ( is_array( $wgProfiler ) && isset( $wgProfiler['class'] ) ) {
728 $class = $wgProfiler['class'];
730 $profiler = new $class(
731 [ 'sampling' => 1, 'output' => [ $output ] ]
733 + [ 'threshold' => $wgProfileLimit ]
734 );
735 $profiler->setTemplated( true );
737 }
738
739 $trxProfiler = Profiler::instance()->getTransactionProfiler();
740 $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
741 $trxProfiler->setExpectations( $wgTrxProfilerLimits['Maintenance'], __METHOD__ );
742 }
743
747 public function clearParamsAndArgs() {
748 $this->mOptions = [];
749 $this->mArgs = [];
750 $this->mInputLoaded = false;
751 }
752
760 public function loadWithArgv( $argv ) {
761 $options = [];
762 $args = [];
763 $this->orderedOptions = [];
764
765 # Parse arguments
766 for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
767 if ( $arg == '--' ) {
768 # End of options, remainder should be considered arguments
769 $arg = next( $argv );
770 while ( $arg !== false ) {
771 $args[] = $arg;
772 $arg = next( $argv );
773 }
774 break;
775 } elseif ( substr( $arg, 0, 2 ) == '--' ) {
776 # Long options
777 $option = substr( $arg, 2 );
778 if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) {
779 $param = next( $argv );
780 if ( $param === false ) {
781 $this->error( "\nERROR: $option parameter needs a value after it\n" );
782 $this->maybeHelp( true );
783 }
784
785 $this->setParam( $options, $option, $param );
786 } else {
787 $bits = explode( '=', $option, 2 );
788 if ( count( $bits ) > 1 ) {
789 $option = $bits[0];
790 $param = $bits[1];
791 } else {
792 $param = 1;
793 }
794
795 $this->setParam( $options, $option, $param );
796 }
797 } elseif ( $arg == '-' ) {
798 # Lonely "-", often used to indicate stdin or stdout.
799 $args[] = $arg;
800 } elseif ( substr( $arg, 0, 1 ) == '-' ) {
801 # Short options
802 $argLength = strlen( $arg );
803 for ( $p = 1; $p < $argLength; $p++ ) {
804 $option = $arg[$p];
805 if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) {
806 $option = $this->mShortParamsMap[$option];
807 }
808
809 if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) {
810 $param = next( $argv );
811 if ( $param === false ) {
812 $this->error( "\nERROR: $option parameter needs a value after it\n" );
813 $this->maybeHelp( true );
814 }
815 $this->setParam( $options, $option, $param );
816 } else {
817 $this->setParam( $options, $option, 1 );
818 }
819 }
820 } else {
821 $args[] = $arg;
822 }
823 }
824
825 $this->mOptions = $options;
826 $this->mArgs = $args;
827 $this->loadSpecialVars();
828 $this->mInputLoaded = true;
829 }
830
843 private function setParam( &$options, $option, $value ) {
844 $this->orderedOptions[] = [ $option, $value ];
845
846 if ( isset( $this->mParams[$option] ) ) {
847 $multi = $this->mParams[$option]['multiOccurrence'];
848 } else {
849 $multi = false;
850 }
851 $exists = array_key_exists( $option, $options );
852 if ( $multi && $exists ) {
853 $options[$option][] = $value;
854 } elseif ( $multi ) {
855 $options[$option] = [ $value ];
856 } elseif ( !$exists ) {
857 $options[$option] = $value;
858 } else {
859 $this->error( "\nERROR: $option parameter given twice\n" );
860 $this->maybeHelp( true );
861 }
862 }
863
873 public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
874 # If we were given opts or args, set those and return early
875 if ( $self ) {
876 $this->mSelf = $self;
877 $this->mInputLoaded = true;
878 }
879 if ( $opts ) {
880 $this->mOptions = $opts;
881 $this->mInputLoaded = true;
882 }
883 if ( $args ) {
884 $this->mArgs = $args;
885 $this->mInputLoaded = true;
886 }
887
888 # If we've already loaded input (either by user values or from $argv)
889 # skip on loading it again. The array_shift() will corrupt values if
890 # it's run again and again
891 if ( $this->mInputLoaded ) {
892 $this->loadSpecialVars();
893
894 return;
895 }
896
897 global $argv;
898 $this->mSelf = $argv[0];
899 $this->loadWithArgv( array_slice( $argv, 1 ) );
900 }
901
905 protected function validateParamsAndArgs() {
906 $die = false;
907 # Check to make sure we've got all the required options
908 foreach ( $this->mParams as $opt => $info ) {
909 if ( $info['require'] && !$this->hasOption( $opt ) ) {
910 $this->error( "Param $opt required!" );
911 $die = true;
912 }
913 }
914 # Check arg list too
915 foreach ( $this->mArgList as $k => $info ) {
916 if ( $info['require'] && !$this->hasArg( $k ) ) {
917 $this->error( 'Argument <' . $info['name'] . '> required!' );
918 $die = true;
919 }
920 }
921
922 if ( $die ) {
923 $this->maybeHelp( true );
924 }
925 }
926
930 protected function loadSpecialVars() {
931 if ( $this->hasOption( 'dbuser' ) ) {
932 $this->mDbUser = $this->getOption( 'dbuser' );
933 }
934 if ( $this->hasOption( 'dbpass' ) ) {
935 $this->mDbPass = $this->getOption( 'dbpass' );
936 }
937 if ( $this->hasOption( 'quiet' ) ) {
938 $this->mQuiet = true;
939 }
940 if ( $this->hasOption( 'batch-size' ) ) {
941 $this->mBatchSize = intval( $this->getOption( 'batch-size' ) );
942 }
943 }
944
949 protected function maybeHelp( $force = false ) {
950 if ( !$force && !$this->hasOption( 'help' ) ) {
951 return;
952 }
953
954 $screenWidth = 80; // TODO: Calculate this!
955 $tab = " ";
956 $descWidth = $screenWidth - ( 2 * strlen( $tab ) );
957
958 ksort( $this->mParams );
959 $this->mQuiet = false;
960
961 // Description ...
962 if ( $this->mDescription ) {
963 $this->output( "\n" . wordwrap( $this->mDescription, $screenWidth ) . "\n" );
964 }
965 $output = "\nUsage: php " . basename( $this->mSelf );
966
967 // ... append parameters ...
968 if ( $this->mParams ) {
969 $output .= " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]";
970 }
971
972 // ... and append arguments.
973 if ( $this->mArgList ) {
974 $output .= ' ';
975 foreach ( $this->mArgList as $k => $arg ) {
976 if ( $arg['require'] ) {
977 $output .= '<' . $arg['name'] . '>';
978 } else {
979 $output .= '[' . $arg['name'] . ']';
980 }
981 if ( $k < count( $this->mArgList ) - 1 ) {
982 $output .= ' ';
983 }
984 }
985 }
986 $this->output( "$output\n\n" );
987
988 # TODO abstract some repetitive code below
989
990 // Generic parameters
991 $this->output( "Generic maintenance parameters:\n" );
992 foreach ( $this->mGenericParameters as $par => $info ) {
993 if ( $info['shortName'] !== false ) {
994 $par .= " (-{$info['shortName']})";
995 }
996 $this->output(
997 wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
998 "\n$tab$tab" ) . "\n"
999 );
1000 }
1001 $this->output( "\n" );
1002
1003 $scriptDependantParams = $this->mDependantParameters;
1004 if ( count( $scriptDependantParams ) > 0 ) {
1005 $this->output( "Script dependant parameters:\n" );
1006 // Parameters description
1007 foreach ( $scriptDependantParams as $par => $info ) {
1008 if ( $info['shortName'] !== false ) {
1009 $par .= " (-{$info['shortName']})";
1010 }
1011 $this->output(
1012 wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
1013 "\n$tab$tab" ) . "\n"
1014 );
1015 }
1016 $this->output( "\n" );
1017 }
1018
1019 // Script specific parameters not defined on construction by
1020 // Maintenance::addDefaultParams()
1021 $scriptSpecificParams = array_diff_key(
1022 # all script parameters:
1024 # remove the Maintenance default parameters:
1027 );
1028 if ( count( $scriptSpecificParams ) > 0 ) {
1029 $this->output( "Script specific parameters:\n" );
1030 // Parameters description
1031 foreach ( $scriptSpecificParams as $par => $info ) {
1032 if ( $info['shortName'] !== false ) {
1033 $par .= " (-{$info['shortName']})";
1034 }
1035 $this->output(
1036 wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
1037 "\n$tab$tab" ) . "\n"
1038 );
1039 }
1040 $this->output( "\n" );
1041 }
1042
1043 // Print arguments
1044 if ( count( $this->mArgList ) > 0 ) {
1045 $this->output( "Arguments:\n" );
1046 // Arguments description
1047 foreach ( $this->mArgList as $info ) {
1048 $openChar = $info['require'] ? '<' : '[';
1049 $closeChar = $info['require'] ? '>' : ']';
1050 $this->output(
1051 wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " .
1052 $info['desc'], $descWidth, "\n$tab$tab" ) . "\n"
1053 );
1054 }
1055 $this->output( "\n" );
1056 }
1057
1058 die( 1 );
1059 }
1060
1064 public function finalSetup() {
1068
1069 # Turn off output buffering again, it might have been turned on in the settings files
1070 if ( ob_get_level() ) {
1071 ob_end_flush();
1072 }
1073 # Same with these
1074 $wgCommandLineMode = true;
1075
1076 # Override $wgServer
1077 if ( $this->hasOption( 'server' ) ) {
1078 $wgServer = $this->getOption( 'server', $wgServer );
1079 }
1080
1081 # If these were passed, use them
1082 if ( $this->mDbUser ) {
1084 }
1085 if ( $this->mDbPass ) {
1087 }
1088
1089 if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) {
1092
1093 if ( $wgDBservers ) {
1097 foreach ( $wgDBservers as $i => $server ) {
1098 $wgDBservers[$i]['user'] = $wgDBuser;
1099 $wgDBservers[$i]['password'] = $wgDBpassword;
1100 }
1101 }
1102 if ( isset( $wgLBFactoryConf['serverTemplate'] ) ) {
1103 $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
1104 $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
1105 }
1106 MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->destroy();
1107 }
1108
1109 // Per-script profiling; useful for debugging
1110 $this->activateProfiler();
1111
1112 $this->afterFinalSetup();
1113
1114 $wgShowSQLErrors = true;
1115
1116 MediaWiki\suppressWarnings();
1117 set_time_limit( 0 );
1118 MediaWiki\restoreWarnings();
1119
1120 $this->adjustMemoryLimit();
1121 }
1122
1126 protected function afterFinalSetup() {
1127 if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
1128 call_user_func( MW_CMDLINE_CALLBACK );
1129 }
1130 }
1131
1136 public function globals() {
1137 if ( $this->hasOption( 'globals' ) ) {
1138 print_r( $GLOBALS );
1139 }
1140 }
1141
1146 public function loadSettings() {
1148
1149 if ( isset( $this->mOptions['conf'] ) ) {
1150 $settingsFile = $this->mOptions['conf'];
1151 } elseif ( defined( "MW_CONFIG_FILE" ) ) {
1152 $settingsFile = MW_CONFIG_FILE;
1153 } else {
1154 $settingsFile = "$IP/LocalSettings.php";
1155 }
1156 if ( isset( $this->mOptions['wiki'] ) ) {
1157 $bits = explode( '-', $this->mOptions['wiki'] );
1158 if ( count( $bits ) == 1 ) {
1159 $bits[] = '';
1160 }
1161 define( 'MW_DB', $bits[0] );
1162 define( 'MW_PREFIX', $bits[1] );
1163 }
1164
1165 if ( !is_readable( $settingsFile ) ) {
1166 $this->error( "A copy of your installation's LocalSettings.php\n" .
1167 "must exist and be readable in the source directory.\n" .
1168 "Use --conf to specify it.", true );
1169 }
1170 $wgCommandLineMode = true;
1171
1172 return $settingsFile;
1173 }
1174
1180 public function purgeRedundantText( $delete = true ) {
1181 # Data should come off the master, wrapped in a transaction
1182 $dbw = $this->getDB( DB_MASTER );
1183 $this->beginTransaction( $dbw, __METHOD__ );
1184
1185 # Get "active" text records from the revisions table
1186 $cur = [];
1187 $this->output( 'Searching for active text records in revisions table...' );
1188 $res = $dbw->select( 'revision', 'rev_text_id', [], __METHOD__, [ 'DISTINCT' ] );
1189 foreach ( $res as $row ) {
1190 $cur[] = $row->rev_text_id;
1191 }
1192 $this->output( "done.\n" );
1193
1194 # Get "active" text records from the archive table
1195 $this->output( 'Searching for active text records in archive table...' );
1196 $res = $dbw->select( 'archive', 'ar_text_id', [], __METHOD__, [ 'DISTINCT' ] );
1197 foreach ( $res as $row ) {
1198 # old pre-MW 1.5 records can have null ar_text_id's.
1199 if ( $row->ar_text_id !== null ) {
1200 $cur[] = $row->ar_text_id;
1201 }
1202 }
1203 $this->output( "done.\n" );
1204
1205 # Get the IDs of all text records not in these sets
1206 $this->output( 'Searching for inactive text records...' );
1207 $cond = 'old_id NOT IN ( ' . $dbw->makeList( $cur ) . ' )';
1208 $res = $dbw->select( 'text', 'old_id', [ $cond ], __METHOD__, [ 'DISTINCT' ] );
1209 $old = [];
1210 foreach ( $res as $row ) {
1211 $old[] = $row->old_id;
1212 }
1213 $this->output( "done.\n" );
1214
1215 # Inform the user of what we're going to do
1216 $count = count( $old );
1217 $this->output( "$count inactive items found.\n" );
1218
1219 # Delete as appropriate
1220 if ( $delete && $count ) {
1221 $this->output( 'Deleting...' );
1222 $dbw->delete( 'text', [ 'old_id' => $old ], __METHOD__ );
1223 $this->output( "done.\n" );
1224 }
1225
1226 # Done
1227 $this->commitTransaction( $dbw, __METHOD__ );
1228 }
1229
1234 protected function getDir() {
1235 return __DIR__;
1236 }
1237
1248 protected function getDB( $db, $groups = [], $wiki = false ) {
1249 if ( is_null( $this->mDb ) ) {
1250 return wfGetDB( $db, $groups, $wiki );
1251 } else {
1252 return $this->mDb;
1253 }
1254 }
1255
1261 public function setDB( IDatabase $db ) {
1262 $this->mDb = $db;
1263 }
1264
1275 protected function beginTransaction( IDatabase $dbw, $fname ) {
1276 $dbw->begin( $fname );
1277 }
1278
1290 protected function commitTransaction( IDatabase $dbw, $fname ) {
1291 $dbw->commit( $fname );
1292 try {
1293 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
1294 $lbFactory->waitForReplication(
1295 [ 'timeout' => 30, 'ifWritesSince' => $this->lastReplicationWait ]
1296 );
1297 $this->lastReplicationWait = microtime( true );
1298
1299 return true;
1300 } catch ( DBReplicationWaitError $e ) {
1301 return false;
1302 }
1303 }
1304
1315 protected function rollbackTransaction( IDatabase $dbw, $fname ) {
1316 $dbw->rollback( $fname );
1317 }
1318
1323 private function lockSearchindex( $db ) {
1324 $write = [ 'searchindex' ];
1325 $read = [
1326 'page',
1327 'revision',
1328 'text',
1329 'interwiki',
1330 'l10n_cache',
1331 'user',
1332 'page_restrictions'
1333 ];
1334 $db->lockTables( $read, $write, __CLASS__ . '::' . __METHOD__ );
1335 }
1336
1341 private function unlockSearchindex( $db ) {
1342 $db->unlockTables( __CLASS__ . '::' . __METHOD__ );
1343 }
1344
1350 private function relockSearchindex( $db ) {
1351 $this->unlockSearchindex( $db );
1352 $this->lockSearchindex( $db );
1353 }
1354
1362 public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) {
1363 $lockTime = time();
1364
1365 # Lock searchindex
1366 if ( $maxLockTime ) {
1367 $this->output( " --- Waiting for lock ---" );
1368 $this->lockSearchindex( $dbw );
1369 $lockTime = time();
1370 $this->output( "\n" );
1371 }
1372
1373 # Loop through the results and do a search update
1374 foreach ( $results as $row ) {
1375 # Allow reads to be processed
1376 if ( $maxLockTime && time() > $lockTime + $maxLockTime ) {
1377 $this->output( " --- Relocking ---" );
1378 $this->relockSearchindex( $dbw );
1379 $lockTime = time();
1380 $this->output( "\n" );
1381 }
1382 call_user_func( $callback, $dbw, $row );
1383 }
1384
1385 # Unlock searchindex
1386 if ( $maxLockTime ) {
1387 $this->output( " --- Unlocking --" );
1388 $this->unlockSearchindex( $dbw );
1389 $this->output( "\n" );
1390 }
1391 }
1392
1399 public function updateSearchIndexForPage( $dbw, $pageId ) {
1400 // Get current revision
1401 $rev = Revision::loadFromPageId( $dbw, $pageId );
1402 $title = null;
1403 if ( $rev ) {
1404 $titleObj = $rev->getTitle();
1405 $title = $titleObj->getPrefixedDBkey();
1406 $this->output( "$title..." );
1407 # Update searchindex
1408 $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getContent() );
1409 $u->doUpdate();
1410 $this->output( "\n" );
1411 }
1412
1413 return $title;
1414 }
1415
1424 public static function posix_isatty( $fd ) {
1425 if ( !function_exists( 'posix_isatty' ) ) {
1426 return !$fd;
1427 } else {
1428 return posix_isatty( $fd );
1429 }
1430 }
1431
1437 public static function readconsole( $prompt = '> ' ) {
1438 static $isatty = null;
1439 if ( is_null( $isatty ) ) {
1440 $isatty = self::posix_isatty( 0 /*STDIN*/ );
1441 }
1442
1443 if ( $isatty && function_exists( 'readline' ) ) {
1444 $resp = readline( $prompt );
1445 if ( $resp === null ) {
1446 // Workaround for https://github.com/facebook/hhvm/issues/4776
1447 return false;
1448 } else {
1449 return $resp;
1450 }
1451 } else {
1452 if ( $isatty ) {
1453 $st = self::readlineEmulation( $prompt );
1454 } else {
1455 if ( feof( STDIN ) ) {
1456 $st = false;
1457 } else {
1458 $st = fgets( STDIN, 1024 );
1459 }
1460 }
1461 if ( $st === false ) {
1462 return false;
1463 }
1464 $resp = trim( $st );
1465
1466 return $resp;
1467 }
1468 }
1469
1475 private static function readlineEmulation( $prompt ) {
1476 $bash = Installer::locateExecutableInDefaultPaths( [ 'bash' ] );
1477 if ( !wfIsWindows() && $bash ) {
1478 $retval = false;
1479 $encPrompt = wfEscapeShellArg( $prompt );
1480 $command = "read -er -p $encPrompt && echo \"\$REPLY\"";
1481 $encCommand = wfEscapeShellArg( $command );
1482 $line = wfShellExec( "$bash -c $encCommand", $retval, [], [ 'walltime' => 0 ] );
1483
1484 if ( $retval == 0 ) {
1485 return $line;
1486 } elseif ( $retval == 127 ) {
1487 // Couldn't execute bash even though we thought we saw it.
1488 // Shell probably spit out an error message, sorry :(
1489 // Fall through to fgets()...
1490 } else {
1491 // EOF/ctrl+D
1492 return false;
1493 }
1494 }
1495
1496 // Fallback... we'll have no editing controls, EWWW
1497 if ( feof( STDIN ) ) {
1498 return false;
1499 }
1500 print $prompt;
1501
1502 return fgets( STDIN, 1024 );
1503 }
1504
1509 public static function requireTestsAutoloader() {
1510 require_once __DIR__ . '/../tests/common/TestsAutoLoader.php';
1511 }
1512}
1513
1518 protected $mSelf = "FakeMaintenanceScript";
1519
1520 public function execute() {
1521 return;
1522 }
1523}
1524
1529abstract class LoggedUpdateMaintenance extends Maintenance {
1530 public function __construct() {
1531 parent::__construct();
1532 $this->addOption( 'force', 'Run the update even if it was completed already' );
1533 $this->setBatchSize( 200 );
1534 }
1535
1536 public function execute() {
1537 $db = $this->getDB( DB_MASTER );
1538 $key = $this->getUpdateKey();
1539
1540 if ( !$this->hasOption( 'force' )
1541 && $db->selectRow( 'updatelog', '1', [ 'ul_key' => $key ], __METHOD__ )
1542 ) {
1543 $this->output( "..." . $this->updateSkippedMessage() . "\n" );
1544
1545 return true;
1546 }
1547
1548 if ( !$this->doDBUpdates() ) {
1549 return false;
1550 }
1551
1552 if ( $db->insert( 'updatelog', [ 'ul_key' => $key ], __METHOD__, 'IGNORE' ) ) {
1553 return true;
1554 } else {
1555 $this->output( $this->updatelogFailedMessage() . "\n" );
1556
1557 return false;
1558 }
1559 }
1560
1565 protected function updateSkippedMessage() {
1566 $key = $this->getUpdateKey();
1567
1568 return "Update '{$key}' already logged as completed.";
1569 }
1570
1575 protected function updatelogFailedMessage() {
1576 $key = $this->getUpdateKey();
1577
1578 return "Unable to log update '{$key}' as completed.";
1579 }
1580
1586 abstract protected function doDBUpdates();
1587
1592 abstract protected function getUpdateKey();
1593}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$GLOBALS['IP']
$wgDBuser
Database username.
$wgDBadminuser
Separate username for maintenance tasks.
$wgTrxProfilerLimits
Performance expectations for DB usage.
$wgDBservers
Database load balancer This is a two-dimensional array, an array of server info structures Fields are...
$wgProfileLimit
Only record profiling info for pages that took longer than this.
$wgDBadminpassword
Separate password for maintenance tasks.
$wgServer
URL of the server.
$wgLBFactoryConf
Load balancer factory configuration To set up a multi-master wiki farm, set the class here to somethi...
$wgDBpassword
Database user's password.
$wgShowSQLErrors
Whether to show "we're sorry, but there has been a database error" pages.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfShellExec( $cmd, &$retval=null, $environ=[], $limits=[], $options=[])
Execute a shell command, with time and memory limits mirrored from the PHP configuration if supported...
wfHostname()
Fetch server name for use in error reporting etc.
wfEscapeShellArg()
Version of escapeshellarg() that works better on Windows.
wfIsWindows()
Check if the operating system is Windows.
$maintClass
const RUN_MAINTENANCE_IF_MAIN
wfEntryPointCheck( $entryPoint)
Check php version and that external dependencies are installed, and display an informative error if e...
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition Setup.php:36
global $wgCommandLineMode
Definition Setup.php:495
$IP
Definition WebStart.php:58
$wgProfiler
Definition WebStart.php:73
float $wgRequestTime
Request start time as fractional seconds since epoch.
Definition WebStart.php:43
$line
Definition cdb.php:59
$command
Definition cdb.php:65
if( $line===false) $args
Definition cdb.php:64
Exception class for replica DB wait timeouts.
Relational database abstraction object.
Definition Database.php:36
Fake maintenance wrapper, mostly used for the web installer/updater.
execute()
Do the actual work.
static locateExecutableInDefaultPaths( $names, $versionInfo=false)
Same as locateExecutable(), but checks in getPossibleBinPaths() by default.
An interface for generating database load balancers.
Definition LBFactory.php:31
Class for scripts that perform database maintenance and want to log the update in updatelog so we can...
__construct()
Default constructor.
doDBUpdates()
Do the actual work.
updatelogFailedMessage()
Message to show that the update log was unable to log the completion of this update.
updateSkippedMessage()
Message to show that the update was done already and was just skipped.
getUpdateKey()
Get the update key name to go in the update log table.
execute()
Do the actual work.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
setup()
Do some sanity checking and basic setup.
__construct()
Default constructor.
error( $err, $die=0)
Throw an error to the user.
const STDIN_ALL
getName()
Get the script's name.
addArg( $arg, $description, $required=true)
Add some args that are needed.
requireExtension( $name)
Indicate that the specified extension must be loaded before the script can run.
relockSearchindex( $db)
Unlock and lock again Since the lock is low-priority, queued reads will be able to complete.
int $mBatchSize
Batch size.
setAgentAndTriggers()
Set triggers like when to try to run deferred updates.
beginTransaction(IDatabase $dbw, $fname)
Begin a transcation on a DB.
clearParamsAndArgs()
Clear all params and arguments.
array $requiredExtensions
setParam(&$options, $option, $value)
Helper function used solely by loadParamsAndArgs to prevent code duplication.
const DB_NONE
Constants for DB access type.
commitTransaction(IDatabase $dbw, $fname)
Commit the transcation on a DB handle and wait for replica DBs to catch up.
output( $out, $channel=null)
Throw some output to the user.
getStdin( $len=null)
Return input from stdin.
cleanupChanneled()
Clean up channeled output.
memoryLimit()
Normally we disable the memory_limit when running admin scripts.
afterFinalSetup()
Execute a callback function at the end of initialisation.
hasArg( $argId=0)
Does a given argument exist?
getDir()
Get the maintenance directory.
addDefaultParams()
Add the default parameters to the scripts.
deleteOption( $name)
Remove an option.
static readlineEmulation( $prompt)
Emulate readline()
static requireTestsAutoloader()
Call this to set up the autoloader to allow classes to be used from the tests directory.
loadParamsAndArgs( $self=null, $opts=null, $args=null)
Process command line arguments $mOptions becomes an array with keys set to the option names $mArgs be...
outputChanneled( $msg, $channel=null)
Message outputter with channeled message support.
finalSetup()
Handle some last-minute setup here.
loadSpecialVars()
Handle the special variables that are global to all scripts.
getDB( $db, $groups=[], $wiki=false)
Returns a database to be used by current maintenance script.
float $lastReplicationWait
UNIX timestamp.
array $orderedOptions
Used to read the options in the order they were passed.
loadSettings()
Generic setup for most installs.
setDB(IDatabase $db)
Sets database object to be returned by getDB().
hasOption( $name)
Checks to see if a particular param exists.
purgeRedundantText( $delete=true)
Support function for cleaning up redundant text records.
Database $mDb
Used by getDB() / setDB()
runChild( $maintClass, $classFile=null)
Run a child maintenance script.
execute()
Do the actual work.
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...
adjustMemoryLimit()
Adjusts PHP's memory limit to better suit our needs, if needed.
validateParamsAndArgs()
Run some validation checks on the params, etc.
getDbType()
Does the script need different DB access? By default, we give Maintenance scripts normal rights to th...
unlockSearchindex( $db)
Unlock the tables.
getArg( $argId=0, $default=null)
Get an argument.
addDescription( $text)
Set the description text.
activateProfiler()
Activate the profiler (assuming $wgProfiler is set)
maybeHelp( $force=false)
Maybe show the help.
updateSearchIndexForPage( $dbw, $pageId)
Update the searchindex table for a given pageid.
resource $fileHandle
Used when creating separate schema files.
loadWithArgv( $argv)
Load params and arguments from a given array of command-line arguments.
static setLBFactoryTriggers(LBFactory $LBFactory)
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
static shouldExecute()
Should we execute the maintenance script, or just allow it to be included as a standalone class?...
getOption( $name, $default=null)
Get an option, or return the default.
Config $config
Accessible via getConfig()
lockSearchindex( $db)
Lock the search index.
checkRequiredExtensions()
Verify that the required extensions are installed.
rollbackTransaction(IDatabase $dbw, $fname)
Rollback the transcation on a DB handle.
updateSearchIndex( $maxLockTime, $callback, $dbw, $results)
Perform a search index update with locking.
globals()
Potentially debug globals.
setConfig(Config $config)
setBatchSize( $s=0)
Set the batch size.
PSR-3 logger instance factory.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static replaceStubInstance(Profiler $profiler)
Replace the current profiler with $profiler if no non-stub profiler is set.
Definition Profiler.php:96
static instance()
Singleton.
Definition Profiler.php:61
static loadFromPageId( $db, $pageid, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition Revision.php:256
Database independant search index updater.
$res
Definition database.txt:21
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling output() to send it all. It could be easily changed to send incrementally if that becomes useful
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
$lbFactory
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:64
the array() calling protocol came about after MediaWiki 1.4rc1.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object & $output
Definition hooks.txt:1102
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account incomplete not yet checked for validity & $retval
Definition hooks.txt:268
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:986
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1096
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition hooks.txt:1135
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition hooks.txt:886
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1734
returning false will NOT prevent logging $e
Definition hooks.txt:2110
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Interface for configuration instances.
Definition Config.php:28
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:34
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
rollback( $fname=__METHOD__, $flush='')
Rollback a transaction previously started using begin().
commit( $fname=__METHOD__, $flush='')
Commits a transaction previously started using begin().
const DB_MASTER
Definition defines.php:23