function wfArrayMap($function, $input) { $ret = array_map($function, $input); foreach ($ret as $key => $value) { $taint = istainted($input[$key]); if ($taint) { taint($ret[$key], $taint); } } return $ret; }
/** * Run an SQL query and return the result. Normally throws a DBQueryError * on failure. If errors are ignored, returns false instead. * * In new code, the query wrappers select(), insert(), update(), delete(), * etc. should be used where possible, since they give much better DBMS * independence and automatically quote or validate user input in a variety * of contexts. This function is generally only useful for queries which are * explicitly DBMS-dependent and are unsupported by the query wrappers, such * as CREATE TABLE. * * However, the query wrappers themselves should call this function. * * @param string $sql : SQL query * @param string $fname : Name of the calling function, for profiling/SHOW PROCESSLIST * comment (you can use __METHOD__ or add some extra info) * @param bool $tempIgnore : Whether to avoid throwing an exception on errors... * maybe best to catch the exception instead? * * @return bool|ResultWrapper true for a successful write query, ResultWrapper object * for a successful read query, or false on failure if $tempIgnore set * * @throws DBQueryError * @throws MWException */ public function query($sql, $fname = '', $tempIgnore = false) { $isMaster = !is_null($this->getLBInfo('master')); if (!Profiler::instance()->isStub()) { # generalizeSQL will probably cut down the query to reasonable # logging size most of the time. The substr is really just a sanity check. if ($isMaster) { $queryProf = 'query-m: ' . substr(DatabaseBase::generalizeSQL($sql), 0, 255); $totalProf = 'DatabaseBase::query-master'; } else { $queryProf = 'query: ' . substr(DatabaseBase::generalizeSQL($sql), 0, 255); $totalProf = 'DatabaseBase::query'; } wfProfileIn($totalProf); wfProfileIn($queryProf); } $this->mLastQuery = $sql; $is_writeable = $this->isWriteQuery($sql); if (!$this->mDoneWrites && $is_writeable) { # Set a flag indicating that writes have been done wfDebug(__METHOD__ . ": Writes done: {$sql}\n"); $this->mDoneWrites = true; } # <Wikia> global $wgDBReadOnly; if ($is_writeable && $wgDBReadOnly) { if (!Profiler::instance()->isStub()) { wfProfileOut($queryProf); wfProfileOut($totalProf); } WikiaLogger::instance()->error('DB readonly mode', ['exception' => new Exception($sql), 'server' => $this->mServer]); wfDebug(sprintf("%s: DB read-only mode prevented the following query: %s\n", __METHOD__, $sql)); return false; } # </Wikia> # Add a comment for easy SHOW PROCESSLIST interpretation global $wgUser; if (is_object($wgUser) && $wgUser->isItemLoaded('name')) { $userName = $wgUser->getName(); if (mb_strlen($userName) > 15) { $userName = mb_substr($userName, 0, 15) . '...'; } $userName = str_replace('/', '', $userName); } else { $userName = ''; } # Wikia change - begin # @author macbre if ($fname === '') { wfDebug("Query: \$fname autogenerated, please pass __METHOD__ to the query() call!\n"); $fname = wfGetCallerClassMethod(['WikiaSQL', 'FluentSql\\SQL', 'WikiaDataAccess', __CLASS__]); } # Wikia change - end $commentedSql = preg_replace('/\\s/', " /* {$fname} {$userName} */ ", $sql, 1); # Wikia change - begin # @author macbre # Add profiling data to queries like BEGIN or COMMIT (preg_replace above will not handle them) if (strpos($sql, ' ') === false) { $commentedSql = "{$sql} /* {$fname} {$userName} */"; } # Wikia change - end # If DBO_TRX is set, start a transaction if ($this->mFlags & DBO_TRX && !$this->trxLevel() && $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK') { # avoid establishing transactions for SHOW and SET statements too - # that would delay transaction initializations to once connection # is really used by application $sqlstart = substr($sql, 0, 10); // very much worth it, benchmark certified(tm) if (strpos($sqlstart, "SHOW ") !== 0 && strpos($sqlstart, "SET ") !== 0) { $this->begin(__METHOD__ . " ({$fname})"); } } if ($this->debug()) { static $cnt = 0; $cnt++; $sqlx = substr($commentedSql, 0, 500); $sqlx = strtr($sqlx, "\t\n", ' '); $master = $isMaster ? 'master' : 'slave'; wfDebug("Query {$this->mDBname} ({$cnt}) ({$master}): {$sqlx}\n"); } if (istainted($sql) & TC_MYSQL) { throw new MWException('Tainted query found'); } $ret = $this->executeAndProfileQuery($commentedSql, $fname, $isMaster); # Try reconnecting if the connection was lost if (false === $ret && $this->wasErrorReissuable()) { # Transaction is gone, like it or not $this->mTrxLevel = 0; wfDebug("Connection lost, reconnecting...\n"); if ($this->ping()) { wfDebug("Reconnected\n"); $sqlx = substr($commentedSql, 0, 500); $sqlx = strtr($sqlx, "\t\n", ' '); global $wgRequestTime; $elapsed = round(microtime(true) - $wgRequestTime, 3); if ($elapsed < 300) { # Not a database error to lose a transaction after a minute or two wfLogDBError("Connection lost and reconnected after {$elapsed}s, query: {$sqlx}\n"); } $ret = $this->executeAndProfileQuery($commentedSql, $fname, $isMaster); } else { wfDebug("Failed\n"); } } if (false === $ret) { $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore); } if (!Profiler::instance()->isStub()) { wfProfileOut($queryProf); wfProfileOut($totalProf); } return $this->resultObject($ret); }
/** * Usually aborts on failure. If errors are explicitly ignored, returns success. * * @param $sql String: SQL query * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST * comment (you can use __METHOD__ or add some extra info) * @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors... * maybe best to catch the exception instead? * @return boolean or ResultWrapper. true for a successful write query, ResultWrapper object for a successful read query, * or false on failure if $tempIgnore set * @throws DBQueryError Thrown when the database returns an error of any kind */ public function query($sql, $fname = '', $tempIgnore = false) { global $wgProfiler; $isMaster = !is_null($this->getLBInfo('master')); if (isset($wgProfiler)) { # generalizeSQL will probably cut down the query to reasonable # logging size most of the time. The substr is really just a sanity check. # Who's been wasting my precious column space? -- TS # $profName = 'query: ' . $fname . ' ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); if ($isMaster) { $queryProf = 'query-m: ' . substr(DatabaseBase::generalizeSQL($sql), 0, 255); $totalProf = 'DatabaseBase::query-master'; } else { $queryProf = 'query: ' . substr(DatabaseBase::generalizeSQL($sql), 0, 255); $totalProf = 'DatabaseBase::query'; } wfProfileIn($totalProf); wfProfileIn($queryProf); } $this->mLastQuery = $sql; if (!$this->mDoneWrites && $this->isWriteQuery($sql)) { // Set a flag indicating that writes have been done wfDebug(__METHOD__ . ": Writes done: {$sql}\n"); $this->mDoneWrites = true; } # Add a comment for easy SHOW PROCESSLIST interpretation # if ( $fname ) { global $wgUser; if (is_object($wgUser) && $wgUser->mDataLoaded) { $userName = $wgUser->getName(); if (mb_strlen($userName) > 15) { $userName = mb_substr($userName, 0, 15) . '...'; } $userName = str_replace('/', '', $userName); } else { $userName = ''; } $commentedSql = preg_replace('/\\s/', " /* {$fname} {$userName} */ ", $sql, 1); # } else { # $commentedSql = $sql; # } # If DBO_TRX is set, start a transaction if ($this->mFlags & DBO_TRX && !$this->trxLevel() && $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK') { // avoid establishing transactions for SHOW and SET statements too - // that would delay transaction initializations to once connection // is really used by application $sqlstart = substr($sql, 0, 10); // very much worth it, benchmark certified(tm) if (strpos($sqlstart, "SHOW ") !== 0 and strpos($sqlstart, "SET ") !== 0) { $this->begin(); } } if ($this->debug()) { static $cnt = 0; $cnt++; $sqlx = substr($commentedSql, 0, 500); $sqlx = strtr($sqlx, "\t\n", ' '); if ($isMaster) { wfDebug("Query {$cnt} (master): {$sqlx}\n"); } else { wfDebug("Query {$cnt} (slave): {$sqlx}\n"); } } if (istainted($sql) & TC_MYSQL) { throw new MWException('Tainted query found'); } # Do the query and handle errors $ret = $this->doQuery($commentedSql); # Try reconnecting if the connection was lost if (false === $ret && $this->wasErrorReissuable()) { # Transaction is gone, like it or not $this->mTrxLevel = 0; wfDebug("Connection lost, reconnecting...\n"); if ($this->ping()) { wfDebug("Reconnected\n"); $sqlx = substr($commentedSql, 0, 500); $sqlx = strtr($sqlx, "\t\n", ' '); global $wgRequestTime; $elapsed = round(microtime(true) - $wgRequestTime, 3); if ($elapsed < 300) { # Not a database error to lose a transaction after a minute or two wfLogDBError("Connection lost and reconnected after {$elapsed}s, query: {$sqlx}\n"); } $ret = $this->doQuery($commentedSql); } else { wfDebug("Failed\n"); } } if (false === $ret) { $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore); } if (isset($wgProfiler)) { wfProfileOut($queryProf); wfProfileOut($totalProf); } return $this->resultObject($ret); }
/** * Run an SQL query and return the result. Normally throws a DBQueryError * on failure. If errors are ignored, returns false instead. * * In new code, the query wrappers select(), insert(), update(), delete(), * etc. should be used where possible, since they give much better DBMS * independence and automatically quote or validate user input in a variety * of contexts. This function is generally only useful for queries which are * explicitly DBMS-dependent and are unsupported by the query wrappers, such * as CREATE TABLE. * * However, the query wrappers themselves should call this function. * * @param $sql String: SQL query * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST * comment (you can use __METHOD__ or add some extra info) * @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors... * maybe best to catch the exception instead? * @throws MWException * @return boolean|ResultWrapper. true for a successful write query, ResultWrapper object * for a successful read query, or false on failure if $tempIgnore set */ public function query($sql, $fname = '', $tempIgnore = false) { $isMaster = !is_null($this->getLBInfo('master')); if (!Profiler::instance()->isStub()) { # generalizeSQL will probably cut down the query to reasonable # logging size most of the time. The substr is really just a sanity check. if ($isMaster) { $queryProf = 'query-m: ' . substr(DatabaseBase::generalizeSQL($sql), 0, 255); $totalProf = 'DatabaseBase::query-master'; } else { $queryProf = 'query: ' . substr(DatabaseBase::generalizeSQL($sql), 0, 255); $totalProf = 'DatabaseBase::query'; } wfProfileIn($totalProf); wfProfileIn($queryProf); } $this->mLastQuery = $sql; if (!$this->mDoneWrites && $this->isWriteQuery($sql)) { # Set a flag indicating that writes have been done wfDebug(__METHOD__ . ": Writes done: {$sql}\n"); $this->mDoneWrites = true; } # Add a comment for easy SHOW PROCESSLIST interpretation global $wgUser; if (is_object($wgUser) && $wgUser->isItemLoaded('name')) { $userName = $wgUser->getName(); if (mb_strlen($userName) > 15) { $userName = mb_substr($userName, 0, 15) . '...'; } $userName = str_replace('/', '', $userName); } else { $userName = ''; } // 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} {$userName} */ ", $sql, 1); # If DBO_TRX is set, start a transaction if ($this->mFlags & DBO_TRX && !$this->mTrxLevel && $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK') { # Avoid establishing transactions for SHOW and SET statements too - # that would delay transaction initializations to once connection # is really used by application $sqlstart = substr($sql, 0, 10); // very much worth it, benchmark certified(tm) if (strpos($sqlstart, "SHOW ") !== 0 && strpos($sqlstart, "SET ") !== 0) { global $wgDebugDBTransactions; if ($wgDebugDBTransactions) { wfDebug("Implicit transaction start.\n"); } $this->begin(__METHOD__ . " ({$fname})"); $this->mTrxAutomatic = true; } } # Keep track of whether the transaction has write queries pending if ($this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery($sql)) { $this->mTrxDoneWrites = true; } if ($this->debug()) { static $cnt = 0; $cnt++; $sqlx = substr($commentedSql, 0, 500); $sqlx = strtr($sqlx, "\t\n", ' '); $master = $isMaster ? 'master' : 'slave'; wfDebug("Query {$this->mDBname} ({$cnt}) ({$master}): {$sqlx}\n"); } if (istainted($sql) & TC_MYSQL) { throw new MWException('Tainted query found'); } $queryId = MWDebug::query($sql, $fname, $isMaster); # Do the query and handle errors $ret = $this->doQuery($commentedSql); MWDebug::queryTime($queryId); # Try reconnecting if the connection was lost if (false === $ret && $this->wasErrorReissuable()) { # Transaction is gone, like it or not $this->mTrxLevel = 0; $this->mTrxIdleCallbacks = array(); // cancel wfDebug("Connection lost, reconnecting...\n"); if ($this->ping()) { wfDebug("Reconnected\n"); $sqlx = substr($commentedSql, 0, 500); $sqlx = strtr($sqlx, "\t\n", ' '); global $wgRequestTime; $elapsed = round(microtime(true) - $wgRequestTime, 3); if ($elapsed < 300) { # Not a database error to lose a transaction after a minute or two wfLogDBError("Connection lost and reconnected after {$elapsed}s, query: {$sqlx}\n"); } $ret = $this->doQuery($commentedSql); } else { wfDebug("Failed\n"); } } if (false === $ret) { $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore); } if (!Profiler::instance()->isStub()) { wfProfileOut($queryProf); wfProfileOut($totalProf); } return $this->resultObject($ret); }