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 : [];
}
+ /**
+ * 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
/**
* @param string $sql A SQL query
- * @return bool Whether $sql is SQL for creating/dropping a new TEMPORARY table
+ * @return bool Whether $sql is SQL for TEMPORARY table operation
*/
protected function registerTempTableOperation( $sql ) {
if ( preg_match(
$sql,
$matches
) ) {
+ $isTemp = isset( $this->mSessionTempTables[$matches[1]] );
unset( $this->mSessionTempTables[$matches[1]] );
- return true;
+ return $isTemp;
+ } elseif ( preg_match(
+ '/^TRUNCATE\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+ $sql,
+ $matches
+ ) ) {
+ return isset( $this->mSessionTempTables[$matches[1]] );
} elseif ( preg_match(
'/^(?:INSERT\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+[`"\']?(\w+)[`"\']?/i',
$sql,
$priorWritesPending = $this->writesOrCallbacksPending();
$this->mLastQuery = $sql;
- $isWrite = $this->isWriteQuery( $sql ) && !$this->registerTempTableOperation( $sql );
+ $isWrite = $this->isWriteQuery( $sql );
if ( $isWrite ) {
+ $isNonTempWrite = !$this->registerTempTableOperation( $sql );
+ } else {
+ $isNonTempWrite = false;
+ }
+
+ if ( $isWrite ) {
+ # 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();
if ( $reason !== false ) {
throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
$this->mLastWriteTime = microtime( true );
}
- // Add trace comment to the begin of the sql string, right after the operator.
- // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598)
+ # Add trace comment to the begin of the sql string, right after the operator.
+ # Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598)
$commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
# Start implicit transactions that wrap the request if DBO_TRX is enabled
$this->assertOpen();
# Send the query to the server
- $ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
+ $ret = $this->doProfiledQuery( $sql, $commentedSql, $isNonTempWrite, $fname );
# Try reconnecting if the connection was lost
if ( false === $ret && $this->wasErrorReissuable() ) {
$this->reportQueryError( $lastError, $lastErrno, $sql, $fname );
} else {
# Should be safe to silently retry the query
- $ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
+ $ret = $this->doProfiledQuery( $sql, $commentedSql, $isNonTempWrite, $fname );
}
} else {
$msg = __METHOD__ . ": lost connection to {$this->getServer()} permanently";
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
}
}
+ /**
+ * 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
return $field;
}
+ public function databasesAreIndependent() {
+ return false;
+ }
+
public function selectDB( $db ) {
# Stub. Shouldn't cause serious problems if it's not overridden, but
# if your database engine supports a concept similar to MySQL's
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 = []
}
/**
- * @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;
}