* @var integer Number of write queries for the current transaction
*/
private $mTrxWriteQueryCount = 0;
+ /**
+ * @var integer Number of rows affected by write queries for the current transaction
+ */
+ private $mTrxWriteAffectedRows = 0;
/**
* @var float Like mTrxWriteQueryCount but excludes lock-bound, easy to replicate, queries
*/
return $conn;
}
+ /**
+ * Set the PSR-3 logger interface to use for query logging. (The logger
+ * interfaces for connection logging and error logging can be set with the
+ * constructor.)
+ *
+ * @param LoggerInterface $logger
+ */
public function setLogger( LoggerInterface $logger ) {
$this->queryLogger = $logger;
}
return $this->mTrxLevel ? $this->mTrxWriteCallers : [];
}
+ public function pendingWriteRowsAffected() {
+ return $this->mTrxWriteAffectedRows;
+ }
+
+ /**
+ * Get the list of method names that have pending write queries or callbacks
+ * for this transaction
+ *
+ * @return array
+ */
protected function pendingWriteAndCallbackCallers() {
if ( !$this->mTrxLevel ) {
return [];
*/
abstract function strencode( $s );
+ /**
+ * Set a custom error handler for logging errors during database connection
+ */
protected function installErrorHandler() {
$this->mPHPError = false;
$this->htmlErrors = ini_set( 'html_errors', '0' );
}
/**
+ * Restore the previous error handler and return the last PHP error for this DB
+ *
* @return bool|string
*/
protected function restoreErrorHandler() {
}
/**
+ * Error handler for logging errors during database connection
* This method should not be used outside of Database classes
*
* @param int $errno
return $res;
}
+ /**
+ * Helper method for query() that handles profiling and logging and sends
+ * the query to doQuery()
+ *
+ * @param string $sql Original SQL query
+ * @param string $commentedSql SQL query with debugging/trace comment
+ * @param bool $isWrite Whether the query is a (non-temporary) write operation
+ * @param string $fname Name of the calling function
+ * @return bool|ResultWrapper True for a successful write query, ResultWrapper
+ * object for a successful read query, or false on failure
+ */
private function doProfiledQuery( $sql, $commentedSql, $isWrite, $fname ) {
$isMaster = !is_null( $this->getLBInfo( 'master' ) );
# generalizeSQL() will probably cut down the query to reasonable
if ( $ret !== false ) {
$this->lastPing = $startTime;
if ( $isWrite && $this->mTrxLevel ) {
- $this->updateTrxWriteQueryTime( $sql, $queryRuntime );
+ $this->updateTrxWriteQueryTime( $sql, $queryRuntime, $this->affectedRows() );
$this->mTrxWriteCallers[] = $fname;
}
}
*
* @param string $sql A SQL write query
* @param float $runtime Total runtime, including RTT
+ * @param integer $affected Affected row count
*/
- private function updateTrxWriteQueryTime( $sql, $runtime ) {
+ private function updateTrxWriteQueryTime( $sql, $runtime, $affected ) {
// Whether this is indicative of replica DB runtime (except for RBR or ws_repl)
$indicativeOfReplicaRuntime = true;
if ( $runtime > self::SLOW_WRITE_SEC ) {
$this->mTrxWriteDuration += $runtime;
$this->mTrxWriteQueryCount += 1;
+ $this->mTrxWriteAffectedRows += $affected;
if ( $indicativeOfReplicaRuntime ) {
$this->mTrxWriteAdjDuration += $runtime;
$this->mTrxWriteAdjQueryCount += 1;
}
}
+ /**
+ * Determine whether or not it is safe to retry queries after a database
+ * connection is lost
+ *
+ * @param string $sql SQL query
+ * @param bool $priorWritesPending Whether there is a transaction open with
+ * possible write queries or transaction pre-commit/idle callbacks
+ * waiting on it to finish.
+ * @return bool True if it is safe to retry the query, false otherwise
+ */
private function canRecoverFromDisconnect( $sql, $priorWritesPending ) {
# Transaction dropped; this can mean lost writes, or REPEATABLE-READ snapshots.
# Dropped connections also mean that named locks are automatically released.
return true;
}
+ /**
+ * Clean things up after transaction loss due to disconnection
+ *
+ * @return null|Exception
+ */
private function handleSessionLoss() {
$this->mTrxLevel = 0;
$this->mTrxIdleCallbacks = []; // T67263
}
public function selectField(
- $table, $var, $cond = '', $fname = __METHOD__, $options = []
+ $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
) {
if ( $var === '*' ) { // sanity
throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
$options['LIMIT'] = 1;
- $res = $this->select( $table, $var, $cond, $fname, $options );
+ $res = $this->select( $table, $var, $cond, $fname, $options, $join_conds );
if ( $res === false || !$this->numRows( $res ) ) {
return false;
}
}
# Split database and table into proper variables.
- # We reverse the explode so that database.table and table both output
- # the correct table.
+ list( $database, $schema, $prefix, $table ) = $this->qualifiedTableComponents( $name );
+
+ # Quote $table and apply the prefix if not quoted.
+ # $tableName might be empty if this is called from Database::replaceVars()
+ $tableName = "{$prefix}{$table}";
+ if ( $format === 'quoted'
+ && !$this->isQuotedIdentifier( $tableName )
+ && $tableName !== ''
+ ) {
+ $tableName = $this->addIdentifierQuotes( $tableName );
+ }
+
+ # Quote $schema and $database and merge them with the table name if needed
+ $tableName = $this->prependDatabaseOrSchema( $schema, $tableName, $format );
+ $tableName = $this->prependDatabaseOrSchema( $database, $tableName, $format );
+
+ return $tableName;
+ }
+
+ /**
+ * @param string $name Table name
+ * @return array (DB name, schema name, table prefix, table name)
+ */
+ protected function qualifiedTableComponents( $name ) {
+ # We reverse the explode so that database.table and table both output the correct table.
$dbDetails = explode( '.', $name, 3 );
if ( count( $dbDetails ) == 3 ) {
list( $database, $schema, $table ) = $dbDetails;
}
}
- # Quote $table and apply the prefix if not quoted.
- # $tableName might be empty if this is called from Database::replaceVars()
- $tableName = "{$prefix}{$table}";
- if ( $format === 'quoted'
- && !$this->isQuotedIdentifier( $tableName )
- && $tableName !== ''
- ) {
- $tableName = $this->addIdentifierQuotes( $tableName );
- }
-
- # Quote $schema and $database and merge them with the table name if needed
- $tableName = $this->prependDatabaseOrSchema( $schema, $tableName, $format );
- $tableName = $this->prependDatabaseOrSchema( $database, $tableName, $format );
-
- return $tableName;
+ return [ $database, $schema, $prefix, $table ];
}
/**
public function insertSelect(
$destTable, $srcTable, $varMap, $conds,
- $fname = __METHOD__, $insertOptions = [], $selectOptions = []
+ $fname = __METHOD__, $insertOptions = [], $selectOptions = [], $selectJoinConds = []
) {
if ( $this->cliMode ) {
// For massive migrations with downtime, we don't want to select everything
$conds,
$fname,
$insertOptions,
- $selectOptions
+ $selectOptions,
+ $selectJoinConds
);
}
$fields[] = $this->fieldNameWithAlias( $sourceColumnOrSql, $dstColumn );
}
$selectOptions[] = 'FOR UPDATE';
- $res = $this->select( $srcTable, implode( ',', $fields ), $conds, $fname, $selectOptions );
+ $res = $this->select(
+ $srcTable, implode( ',', $fields ), $conds, $fname, $selectOptions, $selectJoinConds
+ );
if ( !$res ) {
return false;
}
return $this->insert( $destTable, $rows, $fname, $insertOptions );
}
+ /**
+ * Native server-side implementation of insertSelect() for situations where
+ * we don't want to select everything into memory
+ *
+ * @see IDatabase::insertSelect()
+ */
protected function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
$fname = __METHOD__,
- $insertOptions = [], $selectOptions = []
+ $insertOptions = [], $selectOptions = [], $selectJoinConds = []
) {
$destTable = $this->tableName( $destTable );
$insertOptions = $this->makeInsertOptions( $insertOptions );
- if ( !is_array( $selectOptions ) ) {
- $selectOptions = [ $selectOptions ];
- }
-
- list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) = $this->makeSelectOptions(
- $selectOptions );
-
- if ( is_array( $srcTable ) ) {
- $srcTable = implode( ',', array_map( [ $this, 'tableName' ], $srcTable ) );
- } else {
- $srcTable = $this->tableName( $srcTable );
- }
+ $selectSql = $this->selectSQLText(
+ $srcTable,
+ array_values( $varMap ),
+ $conds,
+ $fname,
+ $selectOptions,
+ $selectJoinConds
+ );
$sql = "INSERT $insertOptions" .
- " INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
- " SELECT $startOpts " . implode( ',', $varMap ) .
- " FROM $srcTable $useIndex $ignoreIndex ";
-
- if ( $conds != '*' ) {
- if ( is_array( $conds ) ) {
- $conds = $this->makeList( $conds, self::LIST_AND );
- }
- $sql .= " WHERE $conds";
- }
-
- $sql .= " $tailOpts";
+ " INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ') ' .
+ $selectSql;
return $this->query( $sql, $fname );
}
$this->mTrxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
$this->mTrxWriteDuration = 0.0;
$this->mTrxWriteQueryCount = 0;
+ $this->mTrxWriteAffectedRows = 0;
$this->mTrxWriteAdjDuration = 0.0;
$this->mTrxWriteAdjQueryCount = 0;
$this->mTrxWriteCallers = [];
if ( $this->mTrxDoneWrites ) {
$this->mLastWriteTime = microtime( true );
$this->trxProfiler->transactionWritingOut(
- $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
+ $this->mServer,
+ $this->mDBname,
+ $this->mTrxShortId,
+ $writeTime,
+ $this->mTrxWriteAffectedRows
+ );
}
$this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
$this->mTrxAtomicLevels = [];
if ( $this->mTrxDoneWrites ) {
$this->trxProfiler->transactionWritingOut(
- $this->mServer, $this->mDBname, $this->mTrxShortId );
+ $this->mServer,
+ $this->mDBname,
+ $this->mTrxShortId
+ );
}
$this->mTrxIdleCallbacks = []; // clear
}
/**
- * @return bool
+ * Close existing database connection and open a new connection
+ *
+ * @return bool True if new connection is opened successfully, false if error
*/
protected function reconnect() {
$this->closeConnection();
return $this->doLockTables( $read, $write, $method );
}
+ /**
+ * Helper function for lockTables() that handles the actual table locking
+ *
+ * @param array $read Array of tables to lock for read access
+ * @param array $write Array of tables to lock for write access
+ * @param string $method Name of caller
+ * @return true
+ */
protected function doLockTables( array $read, array $write, $method ) {
return true;
}
return $this->doUnlockTables( $method );
}
+ /**
+ * Helper function for unlockTables() that handles the actual table unlocking
+ *
+ * @param string $method Name of caller
+ * @return true
+ */
protected function doUnlockTables( $method ) {
return true;
}