+ # Single row case
+ if ( !is_array( reset( $rows ) ) ) {
+ $rows = array( $rows );
+ }
+
+ $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) .') VALUES ';
+ $first = true;
+ foreach ( $rows as $row ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $sql .= ',';
+ }
+ $sql .= '(' . $this->makeList( $row ) . ')';
+ }
+ return $this->query( $sql, $fname );
+ }
+
+ /**
+ * DELETE where the condition is a join
+ * MySQL does this with a multi-table DELETE syntax, PostgreSQL does it with sub-selects
+ *
+ * For safety, an empty $conds will not delete everything. If you want to delete all rows where the
+ * join condition matches, set $conds='*'
+ *
+ * DO NOT put the join condition in $conds
+ *
+ * @param string $delTable The table to delete from.
+ * @param string $joinTable The other table.
+ * @param string $delVar The variable to join on, in the first table.
+ * @param string $joinVar The variable to join on, in the second table.
+ * @param array $conds Condition array of field names mapped to variables, ANDed together in the WHERE clause
+ */
+ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'Database::deleteJoin' ) {
+ if ( !$conds ) {
+ wfDebugDieBacktrace( 'Database::deleteJoin() called with empty $conds' );
+ }
+
+ $delTable = $this->tableName( $delTable );
+ $joinTable = $this->tableName( $joinTable );
+ $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
+ if ( $conds != '*' ) {
+ $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
+ }
+
+ return $this->query( $sql, $fname );
+ }
+
+ /**
+ * Returns the size of a text field, or -1 for "unlimited"
+ */
+ function textFieldSize( $table, $field ) {
+ $table = $this->tableName( $table );
+ $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
+ $res = $this->query( $sql, 'Database::textFieldSize' );
+ $row = $this->fetchObject( $res );
+ $this->freeResult( $res );
+
+ if ( preg_match( "/\((.*)\)/", $row->Type, $m ) ) {
+ $size = $m[1];
+ } else {
+ $size = -1;
+ }
+ return $size;
+ }
+
+ /**
+ * @return string Always return 'LOW_PRIORITY'
+ */
+ function lowPriorityOption() {
+ return 'LOW_PRIORITY';
+ }
+
+ /**
+ * DELETE query wrapper
+ *
+ * Use $conds == "*" to delete all rows
+ */
+ function delete( $table, $conds, $fname = 'Database::delete' ) {
+ if ( !$conds ) {
+ wfDebugDieBacktrace( 'Database::delete() called with no conditions' );
+ }
+ $table = $this->tableName( $table );
+ $sql = "DELETE FROM $table ";
+ if ( $conds != '*' ) {
+ $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
+ }
+ return $this->query( $sql, $fname );
+ }
+
+ /**
+ * INSERT SELECT wrapper
+ * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
+ * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
+ * $conds may be "*" to copy the whole table
+ * srcTable may be an array of tables.
+ */
+ function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'Database::insertSelect' ) {
+ $destTable = $this->tableName( $destTable );
+ if( is_array( $srcTable ) ) {
+ $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
+ } else {
+ $srcTable = $this->tableName( $srcTable );
+ }
+ $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
+ ' SELECT ' . implode( ',', $varMap ) .
+ " FROM $srcTable";
+ if ( $conds != '*' ) {
+ $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+ }
+ return $this->query( $sql, $fname );
+ }
+
+ /**
+ * Construct a LIMIT query with optional offset
+ * This is used for query pages
+ */
+ function limitResult($limit,$offset) {
+ return ' LIMIT '.(is_numeric($offset)?"{$offset},":"")."{$limit} ";
+ }
+
+ /**
+ * Returns an SQL expression for a simple conditional.
+ * Uses IF on MySQL.
+ *
+ * @param string $cond SQL expression which will result in a boolean value
+ * @param string $trueVal SQL expression to return if true
+ * @param string $falseVal SQL expression to return if false
+ * @return string SQL fragment
+ */
+ function conditional( $cond, $trueVal, $falseVal ) {
+ return " IF($cond, $trueVal, $falseVal) ";
+ }
+
+ /**
+ * Determines if the last failure was due to a deadlock
+ */
+ function wasDeadlock() {
+ return $this->lastErrno() == 1213;
+ }
+
+ /**
+ * Perform a deadlock-prone transaction.
+ *
+ * This function invokes a callback function to perform a set of write
+ * queries. If a deadlock occurs during the processing, the transaction
+ * will be rolled back and the callback function will be called again.
+ *
+ * Usage:
+ * $dbw->deadlockLoop( callback, ... );
+ *
+ * Extra arguments are passed through to the specified callback function.
+ *
+ * Returns whatever the callback function returned on its successful,
+ * iteration, or false on error, for example if the retry limit was
+ * reached.
+ */
+ function deadlockLoop() {
+ $myFname = 'Database::deadlockLoop';
+
+ $this->query( 'BEGIN', $myFname );
+ $args = func_get_args();
+ $function = array_shift( $args );
+ $oldIgnore = $dbw->ignoreErrors( true );
+ $tries = DEADLOCK_TRIES;
+ if ( is_array( $function ) ) {
+ $fname = $function[0];
+ } else {
+ $fname = $function;
+ }
+ do {
+ $retVal = call_user_func_array( $function, $args );
+ $error = $this->lastError();
+ $errno = $this->lastErrno();
+ $sql = $this->lastQuery();
+
+ if ( $errno ) {
+ if ( $dbw->wasDeadlock() ) {
+ # Retry
+ usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) );
+ } else {
+ $dbw->reportQueryError( $error, $errno, $sql, $fname );
+ }
+ }
+ } while( $dbw->wasDeadlock && --$tries > 0 );
+ $this->ignoreErrors( $oldIgnore );
+ if ( $tries <= 0 ) {
+ $this->query( 'ROLLBACK', $myFname );
+ $this->reportQueryError( $error, $errno, $sql, $fname );
+ return false;
+ } else {
+ $this->query( 'COMMIT', $myFname );
+ return $retVal;
+ }
+ }
+
+ /**
+ * Do a SELECT MASTER_POS_WAIT()
+ *
+ * @param string $file the binlog file
+ * @param string $pos the binlog position
+ * @param integer $timeout the maximum number of seconds to wait for synchronisation
+ */
+ function masterPosWait( $file, $pos, $timeout ) {
+ $fname = 'Database::masterPosWait';
+ wfProfileIn( $fname );
+
+
+ # Commit any open transactions
+ $this->immediateCommit();
+
+ # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
+ $encFile = $this->strencode( $file );
+ $sql = "SELECT MASTER_POS_WAIT('$encFile', $pos, $timeout)";
+ $res = $this->doQuery( $sql );
+ if ( $res && $row = $this->fetchRow( $res ) ) {
+ $this->freeResult( $res );
+ return $row[0];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get the position of the master from SHOW SLAVE STATUS
+ */
+ function getSlavePos() {
+ $res = $this->query( 'SHOW SLAVE STATUS', 'Database::getSlavePos' );
+ $row = $this->fetchObject( $res );
+ if ( $row ) {
+ return array( $row->Master_Log_File, $row->Read_Master_Log_Pos );
+ } else {
+ return array( false, false );
+ }
+ }
+
+ /**
+ * Get the position of the master from SHOW MASTER STATUS
+ */
+ function getMasterPos() {
+ $res = $this->query( 'SHOW MASTER STATUS', 'Database::getMasterPos' );
+ $row = $this->fetchObject( $res );
+ if ( $row ) {
+ return array( $row->File, $row->Position );
+ } else {
+ return array( false, false );
+ }
+ }
+
+ /**
+ * Begin a transaction, or if a transaction has already started, continue it
+ */
+ function begin( $fname = 'Database::begin' ) {
+ if ( !$this->mTrxLevel ) {
+ $this->immediateBegin( $fname );
+ } else {
+ $this->mTrxLevel++;
+ }
+ }
+
+ /**
+ * End a transaction, or decrement the nest level if transactions are nested
+ */
+ function commit( $fname = 'Database::commit' ) {
+ if ( $this->mTrxLevel ) {
+ $this->mTrxLevel--;
+ }
+ if ( !$this->mTrxLevel ) {
+ $this->immediateCommit( $fname );
+ }
+ }
+
+ /**
+ * Rollback a transaction
+ */
+ function rollback( $fname = 'Database::rollback' ) {
+ $this->query( 'ROLLBACK', $fname );
+ $this->mTrxLevel = 0;
+ }
+
+ /**
+ * Begin a transaction, committing any previously open transaction
+ */
+ function immediateBegin( $fname = 'Database::immediateBegin' ) {
+ $this->query( 'BEGIN', $fname );
+ $this->mTrxLevel = 1;
+ }
+
+ /**
+ * Commit transaction, if one is open
+ */
+ function immediateCommit( $fname = 'Database::immediateCommit' ) {
+ $this->query( 'COMMIT', $fname );
+ $this->mTrxLevel = 0;
+ }
+
+ /**
+ * Return MW-style timestamp used for MySQL schema
+ */
+ function timestamp( $ts=0 ) {
+ return wfTimestamp(TS_MW,$ts);
+ }
+
+ /**
+ * @todo document
+ */
+ function &resultObject( &$result ) {
+ if( empty( $result ) ) {
+ return NULL;
+ } else {
+ return new ResultWrapper( $this, $result );
+ }
+ }
+
+ /**
+ * Return aggregated value alias
+ */
+ function aggregateValue ($valuedata,$valuename='value') {
+ return $valuename;
+ }
+
+ /**
+ * @return string wikitext of a link to the server software's web site
+ */
+ function getSoftwareLink() {
+ return "[http://www.mysql.com/ MySQL]";
+ }
+
+ /**
+ * @return string Version information from the database
+ */
+ function getServerVersion() {
+ return mysql_get_server_info();
+ }
+}
+
+/**
+ * Database abstraction object for mySQL
+ * Inherit all methods and properties of Database::Database()
+ *
+ * @package MediaWiki
+ * @see Database
+ */
+class DatabaseMysql extends Database {
+ # Inherit all
+}
+
+
+/**
+ * Result wrapper for grabbing data queried by someone else
+ *
+ * @package MediaWiki
+ */
+class ResultWrapper {
+ var $db, $result;
+
+ /**
+ * @todo document
+ */
+ function ResultWrapper( $database, $result ) {
+ $this->db =& $database;
+ $this->result =& $result;
+ }
+
+ /**
+ * @todo document
+ */
+ function numRows() {
+ return $this->db->numRows( $this->result );
+ }
+
+ /**
+ * @todo document
+ */
+ function &fetchObject() {
+ return $this->db->fetchObject( $this->result );
+ }
+
+ /**
+ * @todo document
+ */
+ function &fetchRow() {
+ return $this->db->fetchRow( $this->result );
+ }
+
+ /**
+ * @todo document
+ */
+ function free() {
+ $this->db->freeResult( $this->result );
+ unset( $this->result );
+ unset( $this->db );
+ }