public function testAddQuotesStringQuote() { $check = "'string''s cause trouble'"; if ($this->db->getType() === 'mysql') { $check = "'string\\'s cause trouble'"; } $this->assertEquals($check, $this->db->addQuotes("string's cause trouble")); }
/** * Check and repair the destination fields in a link table * @param string $table The link table name * @param string $fieldPrefix The field prefix in the link table * @param int $ns Destination namespace id * @param string $name * @param array $options Associative array of validated command-line options * @param array $extraConds Extra conditions for the SQL query */ private function checkLinkTable($table, $fieldPrefix, $ns, $name, $options, $extraConds = []) { $batchConds = []; $fromField = "{$fieldPrefix}_from"; $namespaceField = "{$fieldPrefix}_namespace"; $titleField = "{$fieldPrefix}_title"; $batchSize = 500; while (true) { $res = $this->db->select($table, [$fromField, $namespaceField, $titleField], array_merge($batchConds, $extraConds, [$namespaceField => 0, $titleField . $this->db->buildLike("{$name}:", $this->db->anyString())]), __METHOD__, ['ORDER BY' => [$titleField, $fromField], 'LIMIT' => $batchSize]); if ($res->numRows() == 0) { break; } foreach ($res as $row) { $logTitle = "from={$row->{$fromField}} ns={$row->{$namespaceField}} " . "dbk={$row->{$titleField}}"; $destTitle = $this->getDestinationTitle($ns, $name, $row->{$namespaceField}, $row->{$titleField}, $options); $this->totalLinks++; if (!$destTitle) { $this->output("{$table} {$logTitle} *** INVALID\n"); continue; } $this->resolvableLinks++; if (!$options['fix']) { $this->output("{$table} {$logTitle} -> " . $destTitle->getPrefixedDBkey() . " DRY RUN\n"); continue; } $this->db->update($table, [$namespaceField => $destTitle->getNamespace(), $titleField => $destTitle->getDBkey()], [$namespaceField => 0, $titleField => $row->{$titleField}, $fromField => $row->{$fromField}], __METHOD__, ['IGNORE']); $this->output("{$table} {$logTitle} -> " . $destTitle->getPrefixedDBkey() . "\n"); } $encLastTitle = $this->db->addQuotes($row->{$titleField}); $encLastFrom = $this->db->addQuotes($row->{$fromField}); $batchConds = ["{$titleField} > {$encLastTitle} " . "OR ({$titleField} = {$encLastTitle} AND {$fromField} > {$encLastFrom})"]; wfWaitForSlaves(); } }
protected function buildUpdateCondition(Database $dbw) { $rcNew = $dbw->addQuotes(RC_NEW); $rcSrcNew = $dbw->addQuotes(RecentChange::SRC_NEW); $rcEdit = $dbw->addQuotes(RC_EDIT); $rcSrcEdit = $dbw->addQuotes(RecentChange::SRC_EDIT); $rcLog = $dbw->addQuotes(RC_LOG); $rcSrcLog = $dbw->addQuotes(RecentChange::SRC_LOG); $rcExternal = $dbw->addQuotes(RC_EXTERNAL); $rcSrcExternal = $dbw->addQuotes(RecentChange::SRC_EXTERNAL); return "rc_source = CASE\n\t\t\t\t\tWHEN rc_type = {$rcNew} THEN {$rcSrcNew}\n\t\t\t\t\tWHEN rc_type = {$rcEdit} THEN {$rcSrcEdit}\n\t\t\t\t\tWHEN rc_type = {$rcLog} THEN {$rcSrcLog}\n\t\t\t\t\tWHEN rc_type = {$rcExternal} THEN {$rcSrcExternal}\n\t\t\t\t\tELSE ''\n\t\t\t\tEND"; }
protected function tearDown() { global $wgRequest, $wgSQLMode; $status = ob_get_status(); if (isset($status['name']) && $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier') { ob_end_flush(); } $this->called['tearDown'] = true; // Cleaning up temporary files foreach ($this->tmpFiles as $fileName) { if (is_file($fileName) || is_link($fileName)) { unlink($fileName); } elseif (is_dir($fileName)) { wfRecursiveRemoveDir($fileName); } } if ($this->needsDB() && $this->db) { // Clean up open transactions while ($this->db->trxLevel() > 0) { $this->db->rollback(__METHOD__, 'flush'); } if ($this->db->getType() === 'mysql') { $this->db->query("SET sql_mode = " . $this->db->addQuotes($wgSQLMode)); } } // Restore mw globals foreach ($this->mwGlobals as $key => $value) { $GLOBALS[$key] = $value; } $this->mwGlobals = []; $this->restoreLoggers(); if (self::$serviceLocator && MediaWikiServices::getInstance() !== self::$serviceLocator) { MediaWikiServices::forceGlobalInstance(self::$serviceLocator); } // TODO: move global state into MediaWikiServices RequestContext::resetMain(); if (session_id() !== '') { session_write_close(); session_id(''); } $wgRequest = new FauxRequest(); MediaWiki\Session\SessionManager::resetCache(); MediaWiki\Auth\AuthManager::resetCache(); $phpErrorLevel = intval(ini_get('error_reporting')); if ($phpErrorLevel !== $this->phpErrorLevel) { ini_set('error_reporting', $this->phpErrorLevel); $oldHex = strtoupper(dechex($this->phpErrorLevel)); $newHex = strtoupper(dechex($phpErrorLevel)); $message = "PHP error_reporting setting was left dirty: " . "was 0x{$oldHex} before test, 0x{$newHex} after test!"; $this->fail($message); } parent::tearDown(); }
/** * Update CategoryLinks collation */ protected function doCollationUpdate() { global $wgCategoryCollation; if ($this->db->fieldExists('categorylinks', 'cl_collation', __METHOD__)) { if ($this->db->selectField('categorylinks', 'COUNT(*)', 'cl_collation != ' . $this->db->addQuotes($wgCategoryCollation), __METHOD__) == 0) { $this->output("...collations up-to-date.\n"); return; } $this->output("Updating category collations..."); $task = $this->maintenance->runChild('UpdateCollation'); $task->execute(); $this->output("...done.\n"); } }
/** * Copy all rows from $srcTable to $dstTable * @param string $srcTable * @param string $dstTable */ function sync($srcTable, $dstTable) { $batchSize = 1000; $minTs = $this->dbw->selectField($srcTable, 'MIN(log_timestamp)', false, __METHOD__); $minTsUnix = wfTimestamp(TS_UNIX, $minTs); $numRowsCopied = 0; while (true) { $maxTs = $this->dbw->selectField($srcTable, 'MAX(log_timestamp)', false, __METHOD__); $copyPos = $this->dbw->selectField($dstTable, 'MAX(log_timestamp)', false, __METHOD__); $maxTsUnix = wfTimestamp(TS_UNIX, $maxTs); $copyPosUnix = wfTimestamp(TS_UNIX, $copyPos); if ($copyPos === null) { $percent = 0; } else { $percent = ($copyPosUnix - $minTsUnix) / ($maxTsUnix - $minTsUnix) * 100; } printf("%s %.2f%%\n", $copyPos, $percent); # Handle all entries with timestamp equal to $copyPos if ($copyPos !== null) { $numRowsCopied += $this->copyExactMatch($srcTable, $dstTable, $copyPos); } # Now copy a batch of rows if ($copyPos === null) { $conds = false; } else { $conds = ['log_timestamp > ' . $this->dbw->addQuotes($copyPos)]; } $srcRes = $this->dbw->select($srcTable, '*', $conds, __METHOD__, ['LIMIT' => $batchSize, 'ORDER BY' => 'log_timestamp']); if (!$srcRes->numRows()) { # All done break; } $batch = []; foreach ($srcRes as $srcRow) { $batch[] = (array) $srcRow; } $this->dbw->insert($dstTable, $batch, __METHOD__); $numRowsCopied += count($batch); wfWaitForSlaves(); } echo "Copied {$numRowsCopied} rows\n"; }
public function addQuotes($s) { if (is_bool($s)) { // Parent would transform to int, which does not play nice with MySQL type juggling. // When searching for an int in a string column, the strings are cast to int, which // means false would match any string not starting with a number. $s = (string) (int) $s; } return parent::addQuotes($s); }
$pendingDBs = $wgMemc->get($mckey); if (!$pendingDBs) { $pendingDBs = array(); # Cross-reference DBs by master DB server $dbsByMaster = array(); $defaultMaster = isset($wgAlternateMaster['DEFAULT']) ? $wgAlternateMaster['DEFAULT'] : $wgDBserver; foreach ($wgLocalDatabases as $db) { if (isset($wgAlternateMaster[$db])) { $dbsByMaster[$wgAlternateMaster[$db]][] = $db; } else { $dbsByMaster[$defaultMaster][] = $db; } } foreach ($dbsByMaster as $master => $dbs) { $dbConn = new Database($master, $wgDBuser, $wgDBpassword, $dbs[0]); $stype = $dbConn->addQuotes($type); # Padding row for MySQL bug $sql = "(SELECT '-------------------------------------------')"; foreach ($dbs as $dbName) { if ($sql != '') { $sql .= ' UNION '; } if ($type === false) { $sql .= "(SELECT '{$dbName}' FROM `{$dbName}`.job LIMIT 1)"; } else { $sql .= "(SELECT '{$dbName}' FROM `{$dbName}`.job WHERE job_cmd={$stype} LIMIT 1)"; } } $res = $dbConn->query($sql, 'nextJobDB.php'); $row = $dbConn->fetchRow($res); // discard padding row
/** * @param string|Blob $s * @return string */ public function addQuotes($s) { if ($s instanceof MssqlBlob) { return $s->fetch(); } elseif ($s instanceof Blob) { // this shouldn't really ever be called, but it's here if needed // (and will quite possibly make the SQL error out) $blob = new MssqlBlob($s->fetch()); return $blob->fetch(); } else { if (is_bool($s)) { $s = $s ? 1 : 0; } return parent::addQuotes($s); } }
protected function populateRevisionOrArchive(Database $dbw, $table, $ns) { $prefix = $table === 'archive' ? 'ar' : 'rev'; $model_column = "{$prefix}_content_model"; $format_column = "{$prefix}_content_format"; $key = "{$prefix}_id"; if ($table === 'archive') { $selectTables = 'archive'; $fields = ['ar_namespace', 'ar_title']; $join_conds = []; $where = $ns === 'all' ? [] : ['ar_namespace' => $ns]; } else { // revision $selectTables = ['revision', 'page']; $fields = ['page_title', 'page_namespace']; $join_conds = ['page' => ['INNER JOIN', 'rev_page=page_id']]; $where = $ns === 'all' ? [] : ['page_namespace' => $ns]; } $toSave = []; $lastId = 0; do { $rows = $dbw->select($selectTables, array_merge($fields, [$model_column, $format_column, $key]), [$model_column => null, "{$key} > " . $dbw->addQuotes($lastId)] + $where, __METHOD__, ['LIMIT' => $this->mBatchSize, 'ORDER BY' => "{$key} ASC"], $join_conds); $this->output("Fetched {$rows->numRows()} rows.\n"); foreach ($rows as $row) { if ($table === 'archive') { $title = Title::makeTitle($row->ar_namespace, $row->ar_title); } else { $title = Title::newFromRow($row); } $lastId = $row->{$key}; try { $handler = ContentHandler::getForTitle($title); } catch (MWException $e) { $this->error("Invalid content model for {$title}"); continue; } $defaultModel = $handler->getModelID(); $defaultFormat = $handler->getDefaultFormat(); $dbModel = $row->{$model_column}; $dbFormat = $row->{$format_column}; $id = $row->{$key}; if ($dbModel === null && $dbFormat === null) { // Set the defaults $toSave[$defaultModel][] = $row->{$key}; } else { // $dbModel === null, $dbFormat set. if ($dbFormat === $defaultFormat) { $toSave[$defaultModel][] = $row->{$key}; } else { // non-default format, just update now $this->output("Updating model to match format for {$table} {$id} of {$title}... "); $dbw->update($table, [$model_column => $defaultModel], [$key => $id], __METHOD__); wfWaitForSlaves(); $this->output("done.\n"); continue; } } if (count($toSave[$defaultModel]) >= $this->mBatchSize) { $this->updateRevisionOrArchiveRows($dbw, $toSave[$defaultModel], $defaultModel, $table); unset($toSave[$defaultModel]); } } } while ($rows->numRows() >= $this->mBatchSize); foreach ($toSave as $model => $ids) { $this->updateRevisionOrArchiveRows($dbw, $ids, $model, $table); } }
/** * @param Database $dbw * @return array */ function getConditions($dbw) { $conds = []; $end = $this->getOption('end', false); $mime = $this->getOption('mime', false); $mediatype = $this->getOption('mediatype', false); $like = $this->getOption('metadata-contains', false); if ($end !== false) { $conds[] = 'img_name <= ' . $dbw->addQuotes($end); } if ($mime !== false) { list($major, $minor) = File::splitMime($mime); $conds['img_major_mime'] = $major; if ($minor !== '*') { $conds['img_minor_mime'] = $minor; } } if ($mediatype !== false) { $conds['img_media_type'] = $mediatype; } if ($like) { $conds[] = 'img_metadata ' . $dbw->buildLike($dbw->anyString(), $like, $dbw->anyString()); } return $conds; }
/** * @param Database $db * @param string $ip * @param string $xfor * @return array conditions */ function getIpConds($db, $ip, $xfor = false) { $type = $xfor ? 'xff' : 'ip'; // IPv4 CIDR, 16-32 bits if (preg_match('#^(\\d+\\.\\d+\\.\\d+\\.\\d+)/(\\d+)$#', $ip, $matches)) { if ($matches[2] < 16 || $matches[2] > 32) { return array('cuc_' . $type . '_hex' => -1); } list($start, $end) = IP::parseRange($ip); return array('cuc_' . $type . '_hex BETWEEN ' . $db->addQuotes($start) . ' AND ' . $db->addQuotes($end)); } else { if (preg_match('#^\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}/(\\d+)$#', $ip, $matches)) { // IPv6 CIDR, 64-128 bits if ($matches[1] < 64 || $matches[1] > 128) { return array('cuc_' . $type . '_hex' => -1); } list($start, $end) = IP::parseRange6($ip); return array('cuc_' . $type . '_hex BETWEEN ' . $db->addQuotes($start) . ' AND ' . $db->addQuotes($end)); } else { if (preg_match('#^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$#', $ip)) { // 32 bit IPv4 $ip_hex = IP::toHex($ip); return array('cuc_' . $type . '_hex' => $ip_hex); } else { if (preg_match('#^\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}$#', $ip)) { // 128 bit IPv6 $ip_hex = IP::toHex($ip); return array('cuc_' . $type . '_hex' => $ip_hex); } else { // throw away this query, incomplete IP, these don't get through the entry point anyway return array('cuc_' . $type . '_hex' => -1); } } } } }
/** * Return an SQL expression selecting rows which sort above the given row, * assuming an ordering of cl_collation, cl_to, cl_type, cl_from * @param stdClass $row * @param Database $dbw * @return string */ function getBatchCondition($row, $dbw) { if ($this->hasOption('previous-collation')) { $fields = ['cl_to', 'cl_type', 'cl_from']; } else { $fields = ['cl_collation', 'cl_to', 'cl_type', 'cl_from']; } $first = true; $cond = false; $prefix = false; foreach ($fields as $field) { if ($dbw->getType() === 'mysql' && $field === 'cl_type') { // Range conditions with enums are weird in mysql // This must be a numeric literal, or it won't work. $encValue = intval($row->cl_type_numeric); } else { $encValue = $dbw->addQuotes($row->{$field}); } $inequality = "{$field} > {$encValue}"; $equality = "{$field} = {$encValue}"; if ($first) { $cond = $inequality; $prefix = $equality; $first = false; } else { $cond .= " OR ({$prefix} AND {$inequality})"; $prefix .= " AND {$equality}"; } } return $cond; }