public function query($sql, $fname = __METHOD__, $tempIgnore = false) { $priorWritesPending = $this->writesOrCallbacksPending(); $this->mLastQuery = $sql; $isWrite = $this->isWriteQuery($sql) && !$this->registerTempTableOperation($sql); if ($isWrite) { $reason = $this->getReadOnlyReason(); if ($reason !== false) { throw new DBReadOnlyError($this, "Database is read-only: {$reason}"); } # Set a flag indicating that writes have been done $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 (bug 42598) $commentedSql = preg_replace('/\\s|$/', " /* {$fname} {$this->agent} */ ", $sql, 1); # Start implicit transactions that wrap the request if DBO_TRX is enabled if (!$this->mTrxLevel && $this->getFlag(self::DBO_TRX) && $this->isTransactableQuery($sql)) { $this->begin(__METHOD__ . " ({$fname})", self::TRANSACTION_INTERNAL); $this->mTrxAutomatic = true; } # Keep track of whether the transaction has write queries pending if ($this->mTrxLevel && !$this->mTrxDoneWrites && $isWrite) { $this->mTrxDoneWrites = true; $this->trxProfiler->transactionWritingIn($this->mServer, $this->mDBname, $this->mTrxShortId); } if ($this->getFlag(self::DBO_DEBUG)) { $this->queryLogger->debug("{$this->mDBname} {$commentedSql}"); } # Avoid fatals if close() was called $this->assertOpen(); # Send the query to the server $ret = $this->doProfiledQuery($sql, $commentedSql, $isWrite, $fname); # Try reconnecting if the connection was lost if (false === $ret && $this->wasErrorReissuable()) { $recoverable = $this->canRecoverFromDisconnect($sql, $priorWritesPending); # Stash the last error values before anything might clear them $lastError = $this->lastError(); $lastErrno = $this->lastErrno(); # 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()); if (!$recoverable) { # Callers may catch the exception and continue to use the DB $this->reportQueryError($lastError, $lastErrno, $sql, $fname); } else { # Should be safe to silently retry the query $ret = $this->doProfiledQuery($sql, $commentedSql, $isWrite, $fname); } } else { $msg = __METHOD__ . ": lost connection to {$this->getServer()} permanently"; $this->connLogger->error($msg); } } if (false === $ret) { # Deadlocks cause the entire transaction to abort, not just the statement. # http://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html # https://www.postgresql.org/docs/9.1/static/explicit-locking.html if ($this->wasDeadlock()) { if ($this->explicitTrxActive() || $priorWritesPending) { $tempIgnore = false; // not recoverable } # Update state tracking to reflect transaction loss $this->handleSessionLoss(); } $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore); } $res = $this->resultObject($ret); return $res; }
/** * Mark a DB as in a transaction with one or more writes pending * * Note that there can be multiple connections to a single DB. * * @param string $server DB server * @param string $db DB name * @param string $id Resource ID string of connection */ public function transactionWritingIn($server, $db, $id = '') { $this->trxProfiler->transactionWritingIn($server, $db, $id); }