X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Flibs%2Frdbms%2Fdatabase%2FDatabase.php;h=bc1454baec765907be2b4f47f22a75dbc14d0c93;hp=e7417eb84a22dc0ead4da0a8eb8aa9d8b02a0d39;hb=4ddb207a32e0235e87954b0d4167c5d7afa85b64;hpb=193317080af67a0c44af86dd9cf0e570103692bc diff --git a/includes/libs/rdbms/database/Database.php b/includes/libs/rdbms/database/Database.php index e7417eb84a..3d40417d65 100644 --- a/includes/libs/rdbms/database/Database.php +++ b/includes/libs/rdbms/database/Database.php @@ -236,6 +236,12 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware /** @var TransactionProfiler */ protected $trxProfiler; + /** + * @var bool Whether writing is allowed on this connection. + * Should be false for connections to replicas. + */ + protected $allowWrite = true; + /** * Constructor and database handle and attempt to connect to the DB server * @@ -277,6 +283,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $this->connLogger = $params['connLogger']; $this->queryLogger = $params['queryLogger']; $this->errorLogger = $params['errorLogger']; + $this->allowWrite = empty( $params['noWrite'] ); // Set initial dummy domain until open() sets the final DB/prefix $this->currentDomain = DatabaseDomain::newUnspecified(); @@ -461,9 +468,12 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware protected function ignoreErrors( $ignoreErrors = null ) { $res = $this->getFlag( self::DBO_IGNORE ); if ( $ignoreErrors !== null ) { - $ignoreErrors - ? $this->setFlag( self::DBO_IGNORE ) - : $this->clearFlag( self::DBO_IGNORE ); + // setFlag()/clearFlag() do not allow DBO_IGNORE changes for sanity + if ( $ignoreErrors ) { + $this->mFlags |= self::DBO_IGNORE; + } else { + $this->mFlags &= ~self::DBO_IGNORE; + } } return $res; @@ -621,6 +631,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware } public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) { + if ( ( $flag & self::DBO_IGNORE ) ) { + throw new \UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." ); + } + if ( $remember === self::REMEMBER_PRIOR ) { array_push( $this->priorFlags, $this->mFlags ); } @@ -628,6 +642,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware } public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) { + if ( ( $flag & self::DBO_IGNORE ) ) { + throw new \UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." ); + } + if ( $remember === self::REMEMBER_PRIOR ) { array_push( $this->priorFlags, $this->mFlags ); } @@ -897,6 +915,13 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware } if ( $isWrite ) { + if ( !$this->allowWrite ) { + throw new DBError( + $this, + 'Write operations are not allowed on this database connection!' + ); + } + # In theory, non-persistent writes are allowed in read-only mode, but due to things # like https://bugs.mysql.com/bug.php?id=33669 that might not work anyway... $reason = $this->getReadOnlyReason(); @@ -945,10 +970,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware # Update state tracking to reflect transaction loss due to disconnection $this->handleSessionLoss(); if ( $this->reconnect() ) { - $msg = __METHOD__ . ": lost connection to {$this->getServer()}; reconnected"; - $this->connLogger->warning( $msg ); - $this->queryLogger->warning( - "$msg:\n" . ( new RuntimeException() )->getTraceAsString() ); + $msg = __METHOD__ . ': lost connection to {dbserver}; reconnected'; + $params = [ 'dbserver' => $this->getServer() ]; + $this->connLogger->warning( $msg, $params ); + $this->queryLogger->warning( $msg, $params + + [ 'trace' => ( new RuntimeException() )->getTraceAsString() ] ); if ( !$recoverable ) { # Callers may catch the exception and continue to use the DB @@ -958,8 +984,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $ret = $this->doProfiledQuery( $sql, $commentedSql, $isNonTempWrite, $fname ); } } else { - $msg = __METHOD__ . ": lost connection to {$this->getServer()} permanently"; - $this->connLogger->error( $msg ); + $msg = __METHOD__ . ': lost connection to {dbserver} permanently'; + $this->connLogger->error( $msg, [ 'dbserver' => $this->getServer() ] ); } } @@ -1138,6 +1164,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware * * @param string $error Error text * @param int $errno Error number + * @return bool */ protected function wasQueryTimeout( $error, $errno ) { return false; @@ -2014,11 +2041,31 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware // No alias? Set it equal to the table name $alias = $table; } + + if ( is_array( $table ) ) { + // A parenthesized group + if ( count( $table ) > 1 ) { + $joinedTable = '(' + . $this->tableNamesWithIndexClauseOrJOIN( $table, $use_index, $ignore_index, $join_conds ) + . ')'; + } else { + // Degenerate case + $innerTable = reset( $table ); + $innerAlias = key( $table ); + $joinedTable = $this->tableNameWithAlias( + $innerTable, + is_string( $innerAlias ) ? $innerAlias : $innerTable + ); + } + } else { + $joinedTable = $this->tableNameWithAlias( $table, $alias ); + } + // Is there a JOIN clause for this table? if ( isset( $join_conds[$alias] ) ) { list( $joinType, $conds ) = $join_conds[$alias]; $tableClause = $joinType; - $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias ); + $tableClause .= ' ' . $joinedTable; if ( isset( $use_index[$alias] ) ) { // has USE INDEX? $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) ); if ( $use != '' ) { @@ -2040,7 +2087,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $retJOIN[] = $tableClause; } elseif ( isset( $use_index[$alias] ) ) { // Is there an INDEX clause for this table? - $tableClause = $this->tableNameWithAlias( $table, $alias ); + $tableClause = $joinedTable; $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) ); @@ -2048,14 +2095,14 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $ret[] = $tableClause; } elseif ( isset( $ignore_index[$alias] ) ) { // Is there an INDEX clause for this table? - $tableClause = $this->tableNameWithAlias( $table, $alias ); + $tableClause = $joinedTable; $tableClause .= ' ' . $this->ignoreIndexClause( implode( ',', (array)$ignore_index[$alias] ) ); $ret[] = $tableClause; } else { - $tableClause = $this->tableNameWithAlias( $table, $alias ); + $tableClause = $joinedTable; $ret[] = $tableClause; } @@ -2407,6 +2454,37 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware ); } + return $this->nonNativeInsertSelect( + $destTable, + $srcTable, + $varMap, + $conds, + $fname, + $insertOptions, + $selectOptions, + $selectJoinConds + ); + } + + /** + * Implementation of insertSelect() based on select() and insert() + * + * @see IDatabase::insertSelect() + * @since 1.30 + * @param string $destTable + * @param string|array $srcTable + * @param array $varMap + * @param array $conds + * @param string $fname + * @param array $insertOptions + * @param array $selectOptions + * @param array $selectJoinConds + * @return bool + */ + protected function nonNativeInsertSelect( $destTable, $srcTable, $varMap, $conds, + $fname = __METHOD__, + $insertOptions = [], $selectOptions = [], $selectJoinConds = [] + ) { // For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden // on only the master (without needing row-based-replication). It also makes it easy to // know how big the INSERT is going to be. @@ -3046,8 +3124,16 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $this->mTrxIdleCallbacks = []; // clear $this->mTrxPreCommitCallbacks = []; // clear - $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK ); - $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK ); + try { + $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK ); + } catch ( Exception $e ) { + // already logged; finish and let LoadBalancer move on during mass-rollback + } + try { + $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK ); + } catch ( Exception $e ) { + // already logged; let LoadBalancer move on during mass-rollback + } } /** @@ -3222,14 +3308,15 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware * @see WANObjectCache::getWithSetCallback() * * @param IDatabase $db1 - * @param IDatabase $dbs,... + * @param IDatabase $db2 [optional] * @return array Map of values: * - lag: highest lag of any of the DBs or false on error (e.g. replication stopped) * - since: oldest UNIX timestamp of any of the DB lag estimates * - pending: whether any of the DBs have uncommitted changes + * @throws DBError * @since 1.27 */ - public static function getCacheSetOptions( IDatabase $db1 ) { + public static function getCacheSetOptions( IDatabase $db1, IDatabase $db2 = null ) { $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ]; foreach ( func_get_args() as $db ) { /** @var IDatabase $db */