[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/db/ -> DatabaseMysqlBase.php (source)

   1  <?php
   2  /**
   3   * This is the MySQL database abstraction layer.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @ingroup Database
  22   */
  23  
  24  /**
  25   * Database abstraction object for MySQL.
  26   * Defines methods independent on used MySQL extension.
  27   *
  28   * @ingroup Database
  29   * @since 1.22
  30   * @see Database
  31   */
  32  abstract class DatabaseMysqlBase extends DatabaseBase {
  33      /** @var MysqlMasterPos */
  34      protected $lastKnownSlavePos;
  35  
  36      /** @var null|int */
  37      protected $mFakeSlaveLag = null;
  38  
  39      protected $mFakeMaster = false;
  40  
  41      /**
  42       * @return string
  43       */
  44  	function getType() {
  45          return 'mysql';
  46      }
  47  
  48      /**
  49       * @param string $server
  50       * @param string $user
  51       * @param string $password
  52       * @param string $dbName
  53       * @throws Exception|DBConnectionError
  54       * @return bool
  55       */
  56  	function open( $server, $user, $password, $dbName ) {
  57          global $wgAllDBsAreLocalhost, $wgSQLMode;
  58          wfProfileIn( __METHOD__ );
  59  
  60          # Debugging hack -- fake cluster
  61          if ( $wgAllDBsAreLocalhost ) {
  62              $realServer = 'localhost';
  63          } else {
  64              $realServer = $server;
  65          }
  66          $this->close();
  67          $this->mServer = $server;
  68          $this->mUser = $user;
  69          $this->mPassword = $password;
  70          $this->mDBname = $dbName;
  71  
  72          wfProfileIn( "dbconnect-$server" );
  73  
  74          # The kernel's default SYN retransmission period is far too slow for us,
  75          # so we use a short timeout plus a manual retry. Retrying means that a small
  76          # but finite rate of SYN packet loss won't cause user-visible errors.
  77          $this->mConn = false;
  78          $this->installErrorHandler();
  79          try {
  80              $this->mConn = $this->mysqlConnect( $realServer );
  81          } catch ( Exception $ex ) {
  82              wfProfileOut( "dbconnect-$server" );
  83              wfProfileOut( __METHOD__ );
  84              $this->restoreErrorHandler();
  85              throw $ex;
  86          }
  87          $error = $this->restoreErrorHandler();
  88  
  89          wfProfileOut( "dbconnect-$server" );
  90  
  91          # Always log connection errors
  92          if ( !$this->mConn ) {
  93              if ( !$error ) {
  94                  $error = $this->lastError();
  95              }
  96              wfLogDBError( "Error connecting to {$this->mServer}: $error" );
  97              wfDebug( "DB connection error\n" .
  98                  "Server: $server, User: $user, Password: " .
  99                  substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
 100  
 101              wfProfileOut( __METHOD__ );
 102  
 103              $this->reportConnectionError( $error );
 104          }
 105  
 106          if ( $dbName != '' ) {
 107              wfSuppressWarnings();
 108              $success = $this->selectDB( $dbName );
 109              wfRestoreWarnings();
 110              if ( !$success ) {
 111                  wfLogDBError( "Error selecting database $dbName on server {$this->mServer}" );
 112                  wfDebug( "Error selecting database $dbName on server {$this->mServer} " .
 113                      "from client host " . wfHostname() . "\n" );
 114  
 115                  wfProfileOut( __METHOD__ );
 116  
 117                  $this->reportConnectionError( "Error selecting database $dbName" );
 118              }
 119          }
 120  
 121          // Tell the server what we're communicating with
 122          if ( !$this->connectInitCharset() ) {
 123              $this->reportConnectionError( "Error setting character set" );
 124          }
 125  
 126          // Set SQL mode, default is turning them all off, can be overridden or skipped with null
 127          if ( is_string( $wgSQLMode ) ) {
 128              $mode = $this->addQuotes( $wgSQLMode );
 129              // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
 130              $success = $this->doQuery( "SET sql_mode = $mode", __METHOD__ );
 131              if ( !$success ) {
 132                  wfLogDBError( "Error setting sql_mode to $mode on server {$this->mServer}" );
 133                  wfProfileOut( __METHOD__ );
 134                  $this->reportConnectionError( "Error setting sql_mode to $mode" );
 135              }
 136          }
 137  
 138          $this->mOpened = true;
 139          wfProfileOut( __METHOD__ );
 140  
 141          return true;
 142      }
 143  
 144      /**
 145       * Set the character set information right after connection
 146       * @return bool
 147       */
 148  	protected function connectInitCharset() {
 149          global $wgDBmysql5;
 150  
 151          if ( $wgDBmysql5 ) {
 152              // Tell the server we're communicating with it in UTF-8.
 153              // This may engage various charset conversions.
 154              return $this->mysqlSetCharset( 'utf8' );
 155          } else {
 156              return $this->mysqlSetCharset( 'binary' );
 157          }
 158      }
 159  
 160      /**
 161       * Open a connection to a MySQL server
 162       *
 163       * @param string $realServer
 164       * @return mixed Raw connection
 165       * @throws DBConnectionError
 166       */
 167      abstract protected function mysqlConnect( $realServer );
 168  
 169      /**
 170       * Set the character set of the MySQL link
 171       *
 172       * @param string $charset
 173       * @return bool
 174       */
 175      abstract protected function mysqlSetCharset( $charset );
 176  
 177      /**
 178       * @param ResultWrapper|resource $res
 179       * @throws DBUnexpectedError
 180       */
 181  	function freeResult( $res ) {
 182          if ( $res instanceof ResultWrapper ) {
 183              $res = $res->result;
 184          }
 185          wfSuppressWarnings();
 186          $ok = $this->mysqlFreeResult( $res );
 187          wfRestoreWarnings();
 188          if ( !$ok ) {
 189              throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
 190          }
 191      }
 192  
 193      /**
 194       * Free result memory
 195       *
 196       * @param resource $res Raw result
 197       * @return bool
 198       */
 199      abstract protected function mysqlFreeResult( $res );
 200  
 201      /**
 202       * @param ResultWrapper|resource $res
 203       * @return stdClass|bool
 204       * @throws DBUnexpectedError
 205       */
 206  	function fetchObject( $res ) {
 207          if ( $res instanceof ResultWrapper ) {
 208              $res = $res->result;
 209          }
 210          wfSuppressWarnings();
 211          $row = $this->mysqlFetchObject( $res );
 212          wfRestoreWarnings();
 213  
 214          $errno = $this->lastErrno();
 215          // Unfortunately, mysql_fetch_object does not reset the last errno.
 216          // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
 217          // these are the only errors mysql_fetch_object can cause.
 218          // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
 219          if ( $errno == 2000 || $errno == 2013 ) {
 220              throw new DBUnexpectedError(
 221                  $this,
 222                  'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() )
 223              );
 224          }
 225  
 226          return $row;
 227      }
 228  
 229      /**
 230       * Fetch a result row as an object
 231       *
 232       * @param resource $res Raw result
 233       * @return stdClass
 234       */
 235      abstract protected function mysqlFetchObject( $res );
 236  
 237      /**
 238       * @param ResultWrapper|resource $res
 239       * @return array|bool
 240       * @throws DBUnexpectedError
 241       */
 242  	function fetchRow( $res ) {
 243          if ( $res instanceof ResultWrapper ) {
 244              $res = $res->result;
 245          }
 246          wfSuppressWarnings();
 247          $row = $this->mysqlFetchArray( $res );
 248          wfRestoreWarnings();
 249  
 250          $errno = $this->lastErrno();
 251          // Unfortunately, mysql_fetch_array does not reset the last errno.
 252          // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
 253          // these are the only errors mysql_fetch_array can cause.
 254          // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
 255          if ( $errno == 2000 || $errno == 2013 ) {
 256              throw new DBUnexpectedError(
 257                  $this,
 258                  'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() )
 259              );
 260          }
 261  
 262          return $row;
 263      }
 264  
 265      /**
 266       * Fetch a result row as an associative and numeric array
 267       *
 268       * @param resource $res Raw result
 269       * @return array
 270       */
 271      abstract protected function mysqlFetchArray( $res );
 272  
 273      /**
 274       * @throws DBUnexpectedError
 275       * @param ResultWrapper|resource $res
 276       * @return int
 277       */
 278  	function numRows( $res ) {
 279          if ( $res instanceof ResultWrapper ) {
 280              $res = $res->result;
 281          }
 282          wfSuppressWarnings();
 283          $n = $this->mysqlNumRows( $res );
 284          wfRestoreWarnings();
 285  
 286          // Unfortunately, mysql_num_rows does not reset the last errno.
 287          // We are not checking for any errors here, since
 288          // these are no errors mysql_num_rows can cause.
 289          // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
 290          // See https://bugzilla.wikimedia.org/42430
 291          return $n;
 292      }
 293  
 294      /**
 295       * Get number of rows in result
 296       *
 297       * @param resource $res Raw result
 298       * @return int
 299       */
 300      abstract protected function mysqlNumRows( $res );
 301  
 302      /**
 303       * @param ResultWrapper|resource $res
 304       * @return int
 305       */
 306  	function numFields( $res ) {
 307          if ( $res instanceof ResultWrapper ) {
 308              $res = $res->result;
 309          }
 310  
 311          return $this->mysqlNumFields( $res );
 312      }
 313  
 314      /**
 315       * Get number of fields in result
 316       *
 317       * @param resource $res Raw result
 318       * @return int
 319       */
 320      abstract protected function mysqlNumFields( $res );
 321  
 322      /**
 323       * @param ResultWrapper|resource $res
 324       * @param int $n
 325       * @return string
 326       */
 327  	function fieldName( $res, $n ) {
 328          if ( $res instanceof ResultWrapper ) {
 329              $res = $res->result;
 330          }
 331  
 332          return $this->mysqlFieldName( $res, $n );
 333      }
 334  
 335      /**
 336       * Get the name of the specified field in a result
 337       *
 338       * @param ResultWrapper|resource $res
 339       * @param int $n
 340       * @return string
 341       */
 342      abstract protected function mysqlFieldName( $res, $n );
 343  
 344      /**
 345       * mysql_field_type() wrapper
 346       * @param ResultWrapper|resource $res
 347       * @param int $n
 348       * @return string
 349       */
 350  	public function fieldType( $res, $n ) {
 351          if ( $res instanceof ResultWrapper ) {
 352              $res = $res->result;
 353          }
 354  
 355          return $this->mysqlFieldType( $res, $n );
 356      }
 357  
 358      /**
 359       * Get the type of the specified field in a result
 360       *
 361       * @param ResultWrapper|resource $res
 362       * @param int $n
 363       * @return string
 364       */
 365      abstract protected function mysqlFieldType( $res, $n );
 366  
 367      /**
 368       * @param ResultWrapper|resource $res
 369       * @param int $row
 370       * @return bool
 371       */
 372  	function dataSeek( $res, $row ) {
 373          if ( $res instanceof ResultWrapper ) {
 374              $res = $res->result;
 375          }
 376  
 377          return $this->mysqlDataSeek( $res, $row );
 378      }
 379  
 380      /**
 381       * Move internal result pointer
 382       *
 383       * @param ResultWrapper|resource $res
 384       * @param int $row
 385       * @return bool
 386       */
 387      abstract protected function mysqlDataSeek( $res, $row );
 388  
 389      /**
 390       * @return string
 391       */
 392  	function lastError() {
 393          if ( $this->mConn ) {
 394              # Even if it's non-zero, it can still be invalid
 395              wfSuppressWarnings();
 396              $error = $this->mysqlError( $this->mConn );
 397              if ( !$error ) {
 398                  $error = $this->mysqlError();
 399              }
 400              wfRestoreWarnings();
 401          } else {
 402              $error = $this->mysqlError();
 403          }
 404          if ( $error ) {
 405              $error .= ' (' . $this->mServer . ')';
 406          }
 407  
 408          return $error;
 409      }
 410  
 411      /**
 412       * Returns the text of the error message from previous MySQL operation
 413       *
 414       * @param resource $conn Raw connection
 415       * @return string
 416       */
 417      abstract protected function mysqlError( $conn = null );
 418  
 419      /**
 420       * @param string $table
 421       * @param array $uniqueIndexes
 422       * @param array $rows
 423       * @param string $fname
 424       * @return ResultWrapper
 425       */
 426  	function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
 427          return $this->nativeReplace( $table, $rows, $fname );
 428      }
 429  
 430      /**
 431       * Estimate rows in dataset
 432       * Returns estimated count, based on EXPLAIN output
 433       * Takes same arguments as Database::select()
 434       *
 435       * @param string|array $table
 436       * @param string|array $vars
 437       * @param string|array $conds
 438       * @param string $fname
 439       * @param string|array $options
 440       * @return bool|int
 441       */
 442  	public function estimateRowCount( $table, $vars = '*', $conds = '',
 443          $fname = __METHOD__, $options = array()
 444      ) {
 445          $options['EXPLAIN'] = true;
 446          $res = $this->select( $table, $vars, $conds, $fname, $options );
 447          if ( $res === false ) {
 448              return false;
 449          }
 450          if ( !$this->numRows( $res ) ) {
 451              return 0;
 452          }
 453  
 454          $rows = 1;
 455          foreach ( $res as $plan ) {
 456              $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
 457          }
 458  
 459          return $rows;
 460      }
 461  
 462      /**
 463       * @param string $table
 464       * @param string $field
 465       * @return bool|MySQLField
 466       */
 467  	function fieldInfo( $table, $field ) {
 468          $table = $this->tableName( $table );
 469          $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
 470          if ( !$res ) {
 471              return false;
 472          }
 473          $n = $this->mysqlNumFields( $res->result );
 474          for ( $i = 0; $i < $n; $i++ ) {
 475              $meta = $this->mysqlFetchField( $res->result, $i );
 476              if ( $field == $meta->name ) {
 477                  return new MySQLField( $meta );
 478              }
 479          }
 480  
 481          return false;
 482      }
 483  
 484      /**
 485       * Get column information from a result
 486       *
 487       * @param resource $res Raw result
 488       * @param int $n
 489       * @return stdClass
 490       */
 491      abstract protected function mysqlFetchField( $res, $n );
 492  
 493      /**
 494       * Get information about an index into an object
 495       * Returns false if the index does not exist
 496       *
 497       * @param string $table
 498       * @param string $index
 499       * @param string $fname
 500       * @return bool|array|null False or null on failure
 501       */
 502  	function indexInfo( $table, $index, $fname = __METHOD__ ) {
 503          # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
 504          # SHOW INDEX should work for 3.x and up:
 505          # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
 506          $table = $this->tableName( $table );
 507          $index = $this->indexName( $index );
 508  
 509          $sql = 'SHOW INDEX FROM ' . $table;
 510          $res = $this->query( $sql, $fname );
 511  
 512          if ( !$res ) {
 513              return null;
 514          }
 515  
 516          $result = array();
 517  
 518          foreach ( $res as $row ) {
 519              if ( $row->Key_name == $index ) {
 520                  $result[] = $row;
 521              }
 522          }
 523  
 524          return empty( $result ) ? false : $result;
 525      }
 526  
 527      /**
 528       * @param string $s
 529       * @return string
 530       */
 531  	function strencode( $s ) {
 532          $sQuoted = $this->mysqlRealEscapeString( $s );
 533  
 534          if ( $sQuoted === false ) {
 535              $this->ping();
 536              $sQuoted = $this->mysqlRealEscapeString( $s );
 537          }
 538  
 539          return $sQuoted;
 540      }
 541  
 542      /**
 543       * MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
 544       *
 545       * @param string $s
 546       * @return string
 547       */
 548  	public function addIdentifierQuotes( $s ) {
 549          // Characters in the range \u0001-\uFFFF are valid in a quoted identifier
 550          // Remove NUL bytes and escape backticks by doubling
 551          return '`' . str_replace( array( "\0", '`' ), array( '', '``' ), $s ) . '`';
 552      }
 553  
 554      /**
 555       * @param string $name
 556       * @return bool
 557       */
 558  	public function isQuotedIdentifier( $name ) {
 559          return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
 560      }
 561  
 562      /**
 563       * @return bool
 564       */
 565  	function ping() {
 566          $ping = $this->mysqlPing();
 567          if ( $ping ) {
 568              return true;
 569          }
 570  
 571          $this->closeConnection();
 572          $this->mOpened = false;
 573          $this->mConn = false;
 574          $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
 575  
 576          return true;
 577      }
 578  
 579      /**
 580       * Ping a server connection or reconnect if there is no connection
 581       *
 582       * @return bool
 583       */
 584      abstract protected function mysqlPing();
 585  
 586      /**
 587       * Set lag time in seconds for a fake slave
 588       *
 589       * @param int $lag
 590       */
 591  	public function setFakeSlaveLag( $lag ) {
 592          $this->mFakeSlaveLag = $lag;
 593      }
 594  
 595      /**
 596       * Make this connection a fake master
 597       *
 598       * @param bool $enabled
 599       */
 600  	public function setFakeMaster( $enabled = true ) {
 601          $this->mFakeMaster = $enabled;
 602      }
 603  
 604      /**
 605       * Returns slave lag.
 606       *
 607       * This will do a SHOW SLAVE STATUS
 608       *
 609       * @return int
 610       */
 611  	function getLag() {
 612          if ( !is_null( $this->mFakeSlaveLag ) ) {
 613              wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
 614  
 615              return $this->mFakeSlaveLag;
 616          }
 617  
 618          return $this->getLagFromSlaveStatus();
 619      }
 620  
 621      /**
 622       * @return bool|int
 623       */
 624  	function getLagFromSlaveStatus() {
 625          $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
 626          if ( !$res ) {
 627              return false;
 628          }
 629          $row = $res->fetchObject();
 630          if ( !$row ) {
 631              return false;
 632          }
 633          if ( strval( $row->Seconds_Behind_Master ) === '' ) {
 634              return false;
 635          } else {
 636              return intval( $row->Seconds_Behind_Master );
 637          }
 638      }
 639  
 640      /**
 641       * Wait for the slave to catch up to a given master position.
 642       * @todo Return values for this and base class are rubbish
 643       *
 644       * @param DBMasterPos|MySQLMasterPos $pos
 645       * @param int $timeout The maximum number of seconds to wait for synchronisation
 646       * @return int Zero if the slave was past that position already,
 647       *   greater than zero if we waited for some period of time, less than
 648       *   zero if we timed out.
 649       */
 650  	function masterPosWait( DBMasterPos $pos, $timeout ) {
 651          if ( $this->lastKnownSlavePos && $this->lastKnownSlavePos->hasReached( $pos ) ) {
 652              return '0'; // http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html
 653          }
 654  
 655          wfProfileIn( __METHOD__ );
 656          # Commit any open transactions
 657          $this->commit( __METHOD__, 'flush' );
 658  
 659          if ( !is_null( $this->mFakeSlaveLag ) ) {
 660              $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
 661  
 662              if ( $wait > $timeout * 1e6 ) {
 663                  wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
 664                  wfProfileOut( __METHOD__ );
 665  
 666                  return -1;
 667              } elseif ( $wait > 0 ) {
 668                  wfDebug( "Fake slave waiting $wait us\n" );
 669                  usleep( $wait );
 670                  wfProfileOut( __METHOD__ );
 671  
 672                  return 1;
 673              } else {
 674                  wfDebug( "Fake slave up to date ($wait us)\n" );
 675                  wfProfileOut( __METHOD__ );
 676  
 677                  return 0;
 678              }
 679          }
 680  
 681          # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
 682          $encFile = $this->addQuotes( $pos->file );
 683          $encPos = intval( $pos->pos );
 684          $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
 685          $res = $this->doQuery( $sql );
 686  
 687          $status = false;
 688          if ( $res && $row = $this->fetchRow( $res ) ) {
 689              $status = $row[0]; // can be NULL, -1, or 0+ per the MySQL manual
 690              if ( ctype_digit( $status ) ) { // success
 691                  $this->lastKnownSlavePos = $pos;
 692              }
 693          }
 694  
 695          wfProfileOut( __METHOD__ );
 696  
 697          return $status;
 698      }
 699  
 700      /**
 701       * Get the position of the master from SHOW SLAVE STATUS
 702       *
 703       * @return MySQLMasterPos|bool
 704       */
 705  	function getSlavePos() {
 706          if ( !is_null( $this->mFakeSlaveLag ) ) {
 707              $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
 708              wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
 709  
 710              return $pos;
 711          }
 712  
 713          $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
 714          $row = $this->fetchObject( $res );
 715  
 716          if ( $row ) {
 717              $pos = isset( $row->Exec_master_log_pos )
 718                  ? $row->Exec_master_log_pos
 719                  : $row->Exec_Master_Log_Pos;
 720  
 721              return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
 722          } else {
 723              return false;
 724          }
 725      }
 726  
 727      /**
 728       * Get the position of the master from SHOW MASTER STATUS
 729       *
 730       * @return MySQLMasterPos|bool
 731       */
 732  	function getMasterPos() {
 733          if ( $this->mFakeMaster ) {
 734              return new MySQLMasterPos( 'fake', microtime( true ) );
 735          }
 736  
 737          $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
 738          $row = $this->fetchObject( $res );
 739  
 740          if ( $row ) {
 741              return new MySQLMasterPos( $row->File, $row->Position );
 742          } else {
 743              return false;
 744          }
 745      }
 746  
 747      /**
 748       * @param string $index
 749       * @return string
 750       */
 751  	function useIndexClause( $index ) {
 752          return "FORCE INDEX (" . $this->indexName( $index ) . ")";
 753      }
 754  
 755      /**
 756       * @return string
 757       */
 758  	function lowPriorityOption() {
 759          return 'LOW_PRIORITY';
 760      }
 761  
 762      /**
 763       * @return string
 764       */
 765  	public function getSoftwareLink() {
 766          // MariaDB includes its name in its version string (sent when the connection is opened),
 767          // and this is how MariaDB's version of the mysql command-line client identifies MariaDB
 768          // servers (see the mariadb_connection() function in libmysql/libmysql.c).
 769          $version = $this->getServerVersion();
 770          if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
 771              return '[{{int:version-db-mariadb-url}} MariaDB]';
 772          }
 773  
 774          // Percona Server's version suffix is not very distinctive, and @@version_comment
 775          // doesn't give the necessary info for source builds, so assume the server is MySQL.
 776          // (Even Percona's version of mysql doesn't try to make the distinction.)
 777          return '[{{int:version-db-mysql-url}} MySQL]';
 778      }
 779  
 780      /**
 781       * @param array $options
 782       */
 783  	public function setSessionOptions( array $options ) {
 784          if ( isset( $options['connTimeout'] ) ) {
 785              $timeout = (int)$options['connTimeout'];
 786              $this->query( "SET net_read_timeout=$timeout" );
 787              $this->query( "SET net_write_timeout=$timeout" );
 788          }
 789      }
 790  
 791      /**
 792       * @param string $sql
 793       * @param string $newLine
 794       * @return bool
 795       */
 796  	public function streamStatementEnd( &$sql, &$newLine ) {
 797          if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
 798              preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
 799              $this->delimiter = $m[1];
 800              $newLine = '';
 801          }
 802  
 803          return parent::streamStatementEnd( $sql, $newLine );
 804      }
 805  
 806      /**
 807       * Check to see if a named lock is available. This is non-blocking.
 808       *
 809       * @param string $lockName Name of lock to poll
 810       * @param string $method Name of method calling us
 811       * @return bool
 812       * @since 1.20
 813       */
 814  	public function lockIsFree( $lockName, $method ) {
 815          $lockName = $this->addQuotes( $lockName );
 816          $result = $this->query( "SELECT IS_FREE_LOCK($lockName) AS lockstatus", $method );
 817          $row = $this->fetchObject( $result );
 818  
 819          return ( $row->lockstatus == 1 );
 820      }
 821  
 822      /**
 823       * @param string $lockName
 824       * @param string $method
 825       * @param int $timeout
 826       * @return bool
 827       */
 828  	public function lock( $lockName, $method, $timeout = 5 ) {
 829          $lockName = $this->addQuotes( $lockName );
 830          $result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
 831          $row = $this->fetchObject( $result );
 832  
 833          if ( $row->lockstatus == 1 ) {
 834              return true;
 835          } else {
 836              wfDebug( __METHOD__ . " failed to acquire lock\n" );
 837  
 838              return false;
 839          }
 840      }
 841  
 842      /**
 843       * FROM MYSQL DOCS:
 844       * http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
 845       * @param string $lockName
 846       * @param string $method
 847       * @return bool
 848       */
 849  	public function unlock( $lockName, $method ) {
 850          $lockName = $this->addQuotes( $lockName );
 851          $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
 852          $row = $this->fetchObject( $result );
 853  
 854          return ( $row->lockstatus == 1 );
 855      }
 856  
 857      /**
 858       * @param array $read
 859       * @param array $write
 860       * @param string $method
 861       * @param bool $lowPriority
 862       * @return bool
 863       */
 864  	public function lockTables( $read, $write, $method, $lowPriority = true ) {
 865          $items = array();
 866  
 867          foreach ( $write as $table ) {
 868              $tbl = $this->tableName( $table ) .
 869                  ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
 870                  ' WRITE';
 871              $items[] = $tbl;
 872          }
 873          foreach ( $read as $table ) {
 874              $items[] = $this->tableName( $table ) . ' READ';
 875          }
 876          $sql = "LOCK TABLES " . implode( ',', $items );
 877          $this->query( $sql, $method );
 878  
 879          return true;
 880      }
 881  
 882      /**
 883       * @param string $method
 884       * @return bool
 885       */
 886  	public function unlockTables( $method ) {
 887          $this->query( "UNLOCK TABLES", $method );
 888  
 889          return true;
 890      }
 891  
 892      /**
 893       * Get search engine class. All subclasses of this
 894       * need to implement this if they wish to use searching.
 895       *
 896       * @return string
 897       */
 898  	public function getSearchEngine() {
 899          return 'SearchMySQL';
 900      }
 901  
 902      /**
 903       * @param bool $value
 904       */
 905  	public function setBigSelects( $value = true ) {
 906          if ( $value === 'default' ) {
 907              if ( $this->mDefaultBigSelects === null ) {
 908                  # Function hasn't been called before so it must already be set to the default
 909                  return;
 910              } else {
 911                  $value = $this->mDefaultBigSelects;
 912              }
 913          } elseif ( $this->mDefaultBigSelects === null ) {
 914              $this->mDefaultBigSelects = (bool)$this->selectField( false, '@@sql_big_selects' );
 915          }
 916          $encValue = $value ? '1' : '0';
 917          $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
 918      }
 919  
 920      /**
 921       * DELETE where the condition is a join. MySql uses multi-table deletes.
 922       * @param string $delTable
 923       * @param string $joinTable
 924       * @param string $delVar
 925       * @param string $joinVar
 926       * @param array|string $conds
 927       * @param bool|string $fname
 928       * @throws DBUnexpectedError
 929       * @return bool|ResultWrapper
 930       */
 931  	function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__ ) {
 932          if ( !$conds ) {
 933              throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
 934          }
 935  
 936          $delTable = $this->tableName( $delTable );
 937          $joinTable = $this->tableName( $joinTable );
 938          $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
 939  
 940          if ( $conds != '*' ) {
 941              $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
 942          }
 943  
 944          return $this->query( $sql, $fname );
 945      }
 946  
 947      /**
 948       * @param string $table
 949       * @param array $rows
 950       * @param array $uniqueIndexes
 951       * @param array $set
 952       * @param string $fname
 953       * @return bool
 954       */
 955  	public function upsert( $table, array $rows, array $uniqueIndexes,
 956          array $set, $fname = __METHOD__
 957      ) {
 958          if ( !count( $rows ) ) {
 959              return true; // nothing to do
 960          }
 961  
 962          if ( !is_array( reset( $rows ) ) ) {
 963              $rows = array( $rows );
 964          }
 965  
 966          $table = $this->tableName( $table );
 967          $columns = array_keys( $rows[0] );
 968  
 969          $sql = "INSERT INTO $table (" . implode( ',', $columns ) . ') VALUES ';
 970          $rowTuples = array();
 971          foreach ( $rows as $row ) {
 972              $rowTuples[] = '(' . $this->makeList( $row ) . ')';
 973          }
 974          $sql .= implode( ',', $rowTuples );
 975          $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, LIST_SET );
 976  
 977          return (bool)$this->query( $sql, $fname );
 978      }
 979  
 980      /**
 981       * Determines how long the server has been up
 982       *
 983       * @return int
 984       */
 985  	function getServerUptime() {
 986          $vars = $this->getMysqlStatus( 'Uptime' );
 987  
 988          return (int)$vars['Uptime'];
 989      }
 990  
 991      /**
 992       * Determines if the last failure was due to a deadlock
 993       *
 994       * @return bool
 995       */
 996  	function wasDeadlock() {
 997          return $this->lastErrno() == 1213;
 998      }
 999  
1000      /**
1001       * Determines if the last failure was due to a lock timeout
1002       *
1003       * @return bool
1004       */
1005  	function wasLockTimeout() {
1006          return $this->lastErrno() == 1205;
1007      }
1008  
1009      /**
1010       * Determines if the last query error was something that should be dealt
1011       * with by pinging the connection and reissuing the query
1012       *
1013       * @return bool
1014       */
1015  	function wasErrorReissuable() {
1016          return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
1017      }
1018  
1019      /**
1020       * Determines if the last failure was due to the database being read-only.
1021       *
1022       * @return bool
1023       */
1024  	function wasReadOnlyError() {
1025          return $this->lastErrno() == 1223 ||
1026              ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
1027      }
1028  
1029      /**
1030       * @param string $oldName
1031       * @param string $newName
1032       * @param bool $temporary
1033       * @param string $fname
1034       * @return bool
1035       */
1036  	function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
1037          $tmp = $temporary ? 'TEMPORARY ' : '';
1038          $newName = $this->addIdentifierQuotes( $newName );
1039          $oldName = $this->addIdentifierQuotes( $oldName );
1040          $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
1041  
1042          return $this->query( $query, $fname );
1043      }
1044  
1045      /**
1046       * List all tables on the database
1047       *
1048       * @param string $prefix Only show tables with this prefix, e.g. mw_
1049       * @param string $fname Calling function name
1050       * @return array
1051       */
1052  	function listTables( $prefix = null, $fname = __METHOD__ ) {
1053          $result = $this->query( "SHOW TABLES", $fname );
1054  
1055          $endArray = array();
1056  
1057          foreach ( $result as $table ) {
1058              $vars = get_object_vars( $table );
1059              $table = array_pop( $vars );
1060  
1061              if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
1062                  $endArray[] = $table;
1063              }
1064          }
1065  
1066          return $endArray;
1067      }
1068  
1069      /**
1070       * @param string $tableName
1071       * @param string $fName
1072       * @return bool|ResultWrapper
1073       */
1074  	public function dropTable( $tableName, $fName = __METHOD__ ) {
1075          if ( !$this->tableExists( $tableName, $fName ) ) {
1076              return false;
1077          }
1078  
1079          return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
1080      }
1081  
1082      /**
1083       * @return array
1084       */
1085  	protected function getDefaultSchemaVars() {
1086          $vars = parent::getDefaultSchemaVars();
1087          $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
1088          $vars['wgDBTableOptions'] = str_replace(
1089              'CHARSET=mysql4',
1090              'CHARSET=binary',
1091              $vars['wgDBTableOptions']
1092          );
1093  
1094          return $vars;
1095      }
1096  
1097      /**
1098       * Get status information from SHOW STATUS in an associative array
1099       *
1100       * @param string $which
1101       * @return array
1102       */
1103  	function getMysqlStatus( $which = "%" ) {
1104          $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
1105          $status = array();
1106  
1107          foreach ( $res as $row ) {
1108              $status[$row->Variable_name] = $row->Value;
1109          }
1110  
1111          return $status;
1112      }
1113  
1114      /**
1115       * Lists VIEWs in the database
1116       *
1117       * @param string $prefix Only show VIEWs with this prefix, eg.
1118       * unit_test_, or $wgDBprefix. Default: null, would return all views.
1119       * @param string $fname Name of calling function
1120       * @return array
1121       * @since 1.22
1122       */
1123  	public function listViews( $prefix = null, $fname = __METHOD__ ) {
1124  
1125          if ( !isset( $this->allViews ) ) {
1126  
1127              // The name of the column containing the name of the VIEW
1128              $propertyName = 'Tables_in_' . $this->mDBname;
1129  
1130              // Query for the VIEWS
1131              $result = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
1132              $this->allViews = array();
1133              while ( ( $row = $this->fetchRow( $result ) ) !== false ) {
1134                  array_push( $this->allViews, $row[$propertyName] );
1135              }
1136          }
1137  
1138          if ( is_null( $prefix ) || $prefix === '' ) {
1139              return $this->allViews;
1140          }
1141  
1142          $filteredViews = array();
1143          foreach ( $this->allViews as $viewName ) {
1144              // Does the name of this VIEW start with the table-prefix?
1145              if ( strpos( $viewName, $prefix ) === 0 ) {
1146                  array_push( $filteredViews, $viewName );
1147              }
1148          }
1149  
1150          return $filteredViews;
1151      }
1152  
1153      /**
1154       * Differentiates between a TABLE and a VIEW.
1155       *
1156       * @param string $name Name of the TABLE/VIEW to test
1157       * @param string $prefix
1158       * @return bool
1159       * @since 1.22
1160       */
1161  	public function isView( $name, $prefix = null ) {
1162          return in_array( $name, $this->listViews( $prefix ) );
1163      }
1164  }
1165  
1166  /**
1167   * Utility class.
1168   * @ingroup Database
1169   */
1170  class MySQLField implements Field {
1171      private $name, $tablename, $default, $max_length, $nullable,
1172          $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary;
1173  
1174  	function __construct( $info ) {
1175          $this->name = $info->name;
1176          $this->tablename = $info->table;
1177          $this->default = $info->def;
1178          $this->max_length = $info->max_length;
1179          $this->nullable = !$info->not_null;
1180          $this->is_pk = $info->primary_key;
1181          $this->is_unique = $info->unique_key;
1182          $this->is_multiple = $info->multiple_key;
1183          $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
1184          $this->type = $info->type;
1185          $this->binary = isset( $info->binary ) ? $info->binary : false;
1186      }
1187  
1188      /**
1189       * @return string
1190       */
1191  	function name() {
1192          return $this->name;
1193      }
1194  
1195      /**
1196       * @return string
1197       */
1198  	function tableName() {
1199          return $this->tableName;
1200      }
1201  
1202      /**
1203       * @return string
1204       */
1205  	function type() {
1206          return $this->type;
1207      }
1208  
1209      /**
1210       * @return bool
1211       */
1212  	function isNullable() {
1213          return $this->nullable;
1214      }
1215  
1216  	function defaultValue() {
1217          return $this->default;
1218      }
1219  
1220      /**
1221       * @return bool
1222       */
1223  	function isKey() {
1224          return $this->is_key;
1225      }
1226  
1227      /**
1228       * @return bool
1229       */
1230  	function isMultipleKey() {
1231          return $this->is_multiple;
1232      }
1233  
1234  	function isBinary() {
1235          return $this->binary;
1236      }
1237  }
1238  
1239  class MySQLMasterPos implements DBMasterPos {
1240      /** @var string */
1241      public $file;
1242  
1243      /** @var int Timestamp */
1244      public $pos;
1245  
1246  	function __construct( $file, $pos ) {
1247          $this->file = $file;
1248          $this->pos = $pos;
1249      }
1250  
1251  	function __toString() {
1252          // e.g db1034-bin.000976/843431247
1253          return "{$this->file}/{$this->pos}";
1254      }
1255  
1256      /**
1257       * @return array|bool (int, int)
1258       */
1259  	protected function getCoordinates() {
1260          $m = array();
1261          if ( preg_match( '!\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
1262              return array( (int)$m[1], (int)$m[2] );
1263          }
1264  
1265          return false;
1266      }
1267  
1268  	function hasReached( MySQLMasterPos $pos ) {
1269          $thisPos = $this->getCoordinates();
1270          $thatPos = $pos->getCoordinates();
1271  
1272          return ( $thisPos && $thatPos && $thisPos >= $thatPos );
1273      }
1274  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1