function doCategoryQuery() { $dbr = wfGetDB(DB_SLAVE, 'category'); if ($this->from != '') { $pageCondition = 'clms_sortkey >= ' . $dbr->addQuotes($this->from); $this->flip = false; } elseif ($this->until != '') { $pageCondition = 'clms_sortkey < ' . $dbr->addQuotes($this->until); $this->flip = true; } else { $pageCondition = '1 = 1'; $this->flip = false; } $res = $dbr->select(array('page', 'categorylinks_multisort', 'category'), array('page_title', 'page_namespace', 'page_len', 'page_is_redirect', 'clms_sortkey', 'cat_id', 'cat_title', 'cat_subcats', 'cat_pages', 'cat_files'), array($pageCondition, 'clms_to' => $this->title->getDBkey(), 'clms_sortkey_name' => $this->sortkeyName), __METHOD__, array('ORDER BY' => $this->flip ? 'clms_sortkey DESC' : 'clms_sortkey', 'USE INDEX' => array('categorylinks_multisort' => 'clms_sortkey'), 'LIMIT' => $this->limit + 1), array('categorylinks_multisort' => array('INNER JOIN', 'clms_from = page_id'), 'category' => array('LEFT JOIN', 'cat_title = page_title AND page_namespace = ' . NS_CATEGORY))); $count = 0; $this->nextPage = null; while ($x = $dbr->fetchObject($res)) { if (++$count > $this->limit) { // We've reached the one extra which shows that there are // additional pages to be had. Stop here... $this->nextPage = $x->clms_sortkey; break; } $title = Title::makeTitle($x->page_namespace, $x->page_title); if ($title->getNamespace() == NS_CATEGORY) { $cat = Category::newFromRow($x, $title); $this->addSubcategoryObject($cat, $x->clms_sortkey, $x->page_len); } elseif ($this->showGallery && $title->getNamespace() == NS_FILE) { $this->addImage($title, $x->clms_sortkey, $x->page_len, $x->page_is_redirect); } else { $this->addPage($title, $x->clms_sortkey, $x->page_len, $x->page_is_redirect); } } }
function doCategoryQuery() { $dbr = wfGetDB(DB_SLAVE, 'category'); $this->nextPage = array('page' => null, 'subcat' => null, 'file' => null); $this->flip = array('page' => false, 'subcat' => false, 'file' => false); foreach (array('page', 'subcat', 'file') as $type) { # Get the sortkeys for start/end, if applicable. Note that if # the collation in the database differs from the one # set in $wgCategoryCollation, pagination might go totally haywire. $extraConds = array('cl_type' => $type); if (isset($this->from[$type]) && $this->from[$type] !== null) { $extraConds[] = 'cl_sortkey >= ' . $dbr->addQuotes($this->collation->getSortKey($this->from[$type])); } elseif (isset($this->until[$type]) && $this->until[$type] !== null) { $extraConds[] = 'cl_sortkey < ' . $dbr->addQuotes($this->collation->getSortKey($this->until[$type])); $this->flip[$type] = true; } $res = $dbr->select(array('page', 'categorylinks', 'category'), array('page_id', 'page_title', 'page_namespace', 'page_len', 'page_is_redirect', 'cl_sortkey', 'cat_id', 'cat_title', 'cat_subcats', 'cat_pages', 'cat_files', 'cl_sortkey_prefix', 'cl_collation'), array_merge(array('cl_to' => $this->title->getDBkey()), $extraConds), __METHOD__, array('USE INDEX' => array('categorylinks' => 'cl_sortkey'), 'LIMIT' => $this->limit + 1, 'ORDER BY' => $this->flip[$type] ? 'cl_sortkey DESC' : 'cl_sortkey'), array('categorylinks' => array('INNER JOIN', 'cl_from = page_id'), 'category' => array('LEFT JOIN', array('cat_title = page_title', 'page_namespace' => NS_CATEGORY)))); $count = 0; foreach ($res as $row) { $title = Title::newFromRow($row); if ($row->cl_collation === '') { // Hack to make sure that while updating from 1.16 schema // and db is inconsistent, that the sky doesn't fall. // See r83544. Could perhaps be removed in a couple decades... $humanSortkey = $row->cl_sortkey; } else { $humanSortkey = $title->getCategorySortkey($row->cl_sortkey_prefix); } if (++$count > $this->limit) { # We've reached the one extra which shows that there # are additional pages to be had. Stop here... $this->nextPage[$type] = $humanSortkey; break; } if ($title->getNamespace() == NS_CATEGORY) { $cat = Category::newFromRow($row, $title); $this->addSubcategoryObject($cat, $humanSortkey, $row->page_len); } elseif ($title->getNamespace() == NS_FILE) { $this->addImage($title, $humanSortkey, $row->page_len, $row->page_is_redirect); } else { $this->addPage($title, $humanSortkey, $row->page_len, $row->page_is_redirect); } } } }
/** * Update all the appropriate counts in the category table, given that * we've added the categories $added and deleted the categories $deleted. * * This should only be called from deferred updates or jobs to avoid contention. * * @param array $added The names of categories that were added * @param array $deleted The names of categories that were deleted * @param integer $id Page ID (this should be the original deleted page ID) */ public function updateCategoryCounts(array $added, array $deleted, $id = 0) { $id = $id ?: $this->getId(); $ns = $this->getTitle()->getNamespace(); $addFields = ['cat_pages = cat_pages + 1']; $removeFields = ['cat_pages = cat_pages - 1']; if ($ns == NS_CATEGORY) { $addFields[] = 'cat_subcats = cat_subcats + 1'; $removeFields[] = 'cat_subcats = cat_subcats - 1'; } elseif ($ns == NS_FILE) { $addFields[] = 'cat_files = cat_files + 1'; $removeFields[] = 'cat_files = cat_files - 1'; } $dbw = wfGetDB(DB_MASTER); if (count($added)) { $existingAdded = $dbw->selectFieldValues('category', 'cat_title', ['cat_title' => $added], __METHOD__); // For category rows that already exist, do a plain // UPDATE instead of INSERT...ON DUPLICATE KEY UPDATE // to avoid creating gaps in the cat_id sequence. if (count($existingAdded)) { $dbw->update('category', $addFields, ['cat_title' => $existingAdded], __METHOD__); } $missingAdded = array_diff($added, $existingAdded); if (count($missingAdded)) { $insertRows = []; foreach ($missingAdded as $cat) { $insertRows[] = ['cat_title' => $cat, 'cat_pages' => 1, 'cat_subcats' => $ns == NS_CATEGORY ? 1 : 0, 'cat_files' => $ns == NS_FILE ? 1 : 0]; } $dbw->upsert('category', $insertRows, ['cat_title'], $addFields, __METHOD__); } } if (count($deleted)) { $dbw->update('category', $removeFields, ['cat_title' => $deleted], __METHOD__); } foreach ($added as $catName) { $cat = Category::newFromName($catName); Hooks::run('CategoryAfterPageAdded', [$cat, $this]); } foreach ($deleted as $catName) { $cat = Category::newFromName($catName); Hooks::run('CategoryAfterPageRemoved', [$cat, $this, $id]); } // Refresh counts on categories that should be empty now, to // trigger possible deletion. Check master for the most // up-to-date cat_pages. if (count($deleted)) { $rows = $dbw->select('category', ['cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files'], ['cat_title' => $deleted, 'cat_pages <= 0'], __METHOD__); foreach ($rows as $row) { $cat = Category::newFromRow($row); $cat->refreshCounts(); } } }
/** * Returns a string with an HTML representation of the children of the given category. * @param $title Title * @param $depth int * @return string */ function renderChildren($title, $depth = 1) { global $wgCategoryTreeMaxChildren, $wgCategoryTreeUseCategoryTable; if ($title->getNamespace() != NS_CATEGORY) { // Non-categories can't have children. :) return ''; } $dbr = wfGetDB(DB_SLAVE); $inverse = $this->isInverse(); $mode = $this->getOption('mode'); $namespaces = $this->getOption('namespaces'); $tables = array('page', 'categorylinks'); $fields = array('page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest', 'cl_to', 'cl_from'); $where = array(); $joins = array(); $options = array('ORDER BY' => 'cl_type, cl_sortkey', 'LIMIT' => $wgCategoryTreeMaxChildren); if ($inverse) { $joins['categorylinks'] = array('RIGHT JOIN', array('cl_to = page_title', 'page_namespace' => NS_CATEGORY)); $where['cl_from'] = $title->getArticleId(); } else { $joins['categorylinks'] = array('JOIN', 'cl_from = page_id'); $where['cl_to'] = $title->getDBkey(); $options['USE INDEX']['categorylinks'] = 'cl_sortkey'; # namespace filter. if ($namespaces) { # NOTE: we assume that the $namespaces array contains only integers! decodeNamepsaces makes it so. $where['page_namespace'] = $namespaces; } elseif ($mode != CT_MODE_ALL) { if ($mode == CT_MODE_PAGES) { $where['cl_type'] = array('page', 'subcat'); } else { $where['cl_type'] = 'subcat'; } } } # fetch member count if possible $doCount = !$inverse && $wgCategoryTreeUseCategoryTable; if ($doCount) { $tables = array_merge($tables, array('category')); $fields = array_merge($fields, array('cat_id', 'cat_title', 'cat_subcats', 'cat_pages', 'cat_files')); $joins['category'] = array('LEFT JOIN', array('cat_title = page_title', 'page_namespace' => NS_CATEGORY)); } $res = $dbr->select($tables, $fields, $where, __METHOD__, $options, $joins); # collect categories separately from other pages $categories = ''; $other = ''; foreach ($res as $row) { # NOTE: in inverse mode, the page record may be null, because we use a right join. # happens for categories with no category page (red cat links) if ($inverse && $row->page_title === null) { $t = Title::makeTitle(NS_CATEGORY, $row->cl_to); } else { # TODO: translation support; ideally added to Title object $t = Title::newFromRow($row); } $cat = null; if ($doCount && $row->page_namespace == NS_CATEGORY) { $cat = Category::newFromRow($row, $t); } $s = $this->renderNodeInfo($t, $cat, $depth - 1, false); $s .= "\n\t\t"; if ($row->page_namespace == NS_CATEGORY) { $categories .= $s; } else { $other .= $s; } } return $categories . $other; }
/** * Returns a string with an HTML representation of the children of the given category. * $title must be a Title object */ function renderChildren(&$title, $depth = 1) { global $wgCategoryTreeMaxChildren, $wgCategoryTreeUseCategoryTable; if ($title->getNamespace() != NS_CATEGORY) { // Non-categories can't have children. :) return ''; } $dbr =& wfGetDB(DB_SLAVE); $inverse = $this->isInverse(); $mode = $this->getOption('mode'); $namespaces = $this->getOption('namespaces'); if ($inverse) { $ctJoinCond = ' cl_to = cat.page_title AND cat.page_namespace = ' . NS_CATEGORY; $ctWhere = ' cl_from = ' . $title->getArticleId(); $ctJoin = ' RIGHT JOIN '; $nsmatch = ''; } else { $ctJoinCond = ' cl_from = cat.page_id '; $ctWhere = ' cl_to = ' . $dbr->addQuotes($title->getDBkey()); $ctJoin = ' JOIN '; #namespace filter. if ($namespaces) { #NOTE: we assume that the $namespaces array contains only integers! decodeNamepsaces makes it so. if (sizeof($namespaces) === 1) { $nsmatch = ' AND cat.page_namespace = ' . $namespaces[0] . ' '; } else { $nsmatch = ' AND cat.page_namespace IN ( ' . implode(', ', $namespaces) . ') '; } } else { if ($mode == CT_MODE_ALL) { $nsmatch = ''; } else { if ($mode == CT_MODE_PAGES) { $nsmatch = ' AND cat.page_namespace != ' . NS_IMAGE; } else { $nsmatch = ' AND cat.page_namespace = ' . NS_CATEGORY; } } } } #additional stuff to be used if "transaltion" by interwiki-links is desired $transFields = ''; $transJoin = ''; $transWhere = ''; # fetch member count if possible $doCount = !$inverse && $wgCategoryTreeUseCategoryTable; $countFields = ''; $countJoin = ''; if ($doCount) { $cat = $dbr->tableName('category'); $countJoin = " LEFT JOIN {$cat} ON cat_title = page_title AND page_namespace = " . NS_CATEGORY; $countFields = ', cat_id, cat_title, cat_subcats, cat_pages, cat_files'; } $page = $dbr->tableName('page'); $categorylinks = $dbr->tableName('categorylinks'); $sql = "SELECT cat.page_namespace, cat.page_title,\r\n\t\t\t\tcl_to, cl_from\r\n\t\t\t\t\t {$transFields}\r\n\t\t\t\t\t {$countFields}\r\n\t\t\t\tFROM {$page} as cat\r\n\t\t\t\t{$ctJoin} {$categorylinks} ON {$ctJoinCond}\r\n\t\t\t\t{$transJoin}\r\n\t\t\t\t{$countJoin}\r\n\t\t\t\tWHERE {$ctWhere}\r\n\t\t\t\t{$nsmatch}\r\n\t\t\t\t" . "\r\n\t\t\t\t{$transWhere}\r\n\t\t\t\tORDER BY cl_sortkey\r\n\t\t\t\tLIMIT " . (int) $wgCategoryTreeMaxChildren; $res = $dbr->query($sql, __METHOD__); #collect categories separately from other pages $categories = ''; $other = ''; while ($row = $dbr->fetchObject($res)) { #NOTE: in inverse mode, the page record may be null, because we use a right join. # happens for categories with no category page (red cat links) if ($inverse && $row->page_title === NULL) { $t = Title::makeTitle(NS_CATEGORY, $row->cl_to); } else { #TODO: translation support; ideally added to Title object $t = Title::newFromRow($row); } $cat = NULL; if ($doCount && $row->page_namespace == NS_CATEGORY) { $cat = Category::newFromRow($row, $t); } $s = $this->renderNodeInfo($t, $cat, $depth - 1, false); $s .= "\n\t\t"; if ($row->page_namespace == NS_CATEGORY) { $categories .= $s; } else { $other .= $s; } } $dbr->freeResult($res); return $categories . $other; }
function doCategoryQuery() { $dbr = wfGetDB(DB_SLAVE, 'vslow'); if ($this->fromSortKey != '') { $pageCondition = 'cl_sortkey >= ' . $dbr->addQuotes($this->fromSortKey); $this->flip = false; } elseif ($this->untilSortKey != '') { $pageCondition = 'cl_sortkey < ' . $dbr->addQuotes($this->untilSortKey); $this->flip = true; } else { $pageCondition = '1 = 1'; $this->flip = false; } $res = $dbr->select(array('page', 'categorylinks', 'category'), array('page_title', 'page_namespace', 'page_len', 'page_is_redirect', 'cl_sortkey', 'cat_id', 'cat_title', 'cat_subcats', 'cat_pages', 'cat_files', 'cat_id IS NULL as cat_id_null'), array($pageCondition, 'cl_to' => $this->title->getDBkey()), __METHOD__, array('ORDER BY' => $this->flip ? 'cat_id_null asc, cl_sortkey DESC' : 'cat_id_null asc, cl_sortkey', 'USE INDEX' => array('categorylinks' => 'cl_sortkey'), 'LIMIT' => $this->limit + 1), array('categorylinks' => array('INNER JOIN', 'cl_from = page_id'), 'category' => array('LEFT JOIN', 'cat_title = page_title AND page_namespace = ' . NS_CATEGORY))); $count = 0; while ($x = $dbr->fetchObject($res)) { if (++$count > $this->limit) { // We've reached the one extra which shows that there are // additional pages to be had. Stop here... break; } $title = Title::makeTitle($x->page_namespace, $x->page_title); if ($title->getNamespace() == NS_CATEGORY) { $cat = Category::newFromRow($x, $title); $this->addSubcategoryObject($cat, $x->cl_sortkey, $x->page_len); } elseif ($this->showGallery && $title->getNamespace() == NS_FILE) { $this->addImage($title, $x->cl_sortkey, $x->page_len, $x->page_is_redirect); } else { if (wfRunHooks("CategoryViewer::addPage", array(&$this, &$title, &$x, $x->cl_sortkey))) { $this->addPage($title, $x->cl_sortkey, $x->page_len, $x->page_is_redirect); } } } $dbr->freeResult($res); }
public function doUpdate() { $services = MediaWikiServices::getInstance(); $config = $services->getMainConfig(); $lbFactory = $services->getDBLoadBalancerFactory(); $batchSize = $config->get('UpdateRowsPerQuery'); // Page may already be deleted, so don't just getId() $id = $this->pageId; if ($this->ticket) { // Make sure all links update threads see the changes of each other. // This handles the case when updates have to batched into several COMMITs. $scopedLock = LinksUpdate::acquirePageLock($this->getDB(), $id); } $title = $this->page->getTitle(); $dbw = $this->getDB(); // convenience // Delete restrictions for it $dbw->delete('page_restrictions', ['pr_page' => $id], __METHOD__); // Fix category table counts $cats = $dbw->selectFieldValues('categorylinks', 'cl_to', ['cl_from' => $id], __METHOD__); $catBatches = array_chunk($cats, $batchSize); foreach ($catBatches as $catBatch) { $this->page->updateCategoryCounts([], $catBatch, $id); if (count($catBatches) > 1) { $lbFactory->commitAndWaitForReplication(__METHOD__, $this->ticket, ['wiki' => $dbw->getWikiID()]); } } // Refresh the category table entry if it seems to have no pages. Check // master for the most up-to-date cat_pages count. if ($title->getNamespace() === NS_CATEGORY) { $row = $dbw->selectRow('category', ['cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files'], ['cat_title' => $title->getDBkey(), 'cat_pages <= 0'], __METHOD__); if ($row) { Category::newFromRow($row, $title)->refreshCounts(); } } $this->batchDeleteByPK('pagelinks', ['pl_from' => $id], ['pl_from', 'pl_namespace', 'pl_title'], $batchSize); $this->batchDeleteByPK('imagelinks', ['il_from' => $id], ['il_from', 'il_to'], $batchSize); $this->batchDeleteByPK('categorylinks', ['cl_from' => $id], ['cl_from', 'cl_to'], $batchSize); $this->batchDeleteByPK('templatelinks', ['tl_from' => $id], ['tl_from', 'tl_namespace', 'tl_title'], $batchSize); $this->batchDeleteByPK('externallinks', ['el_from' => $id], ['el_id'], $batchSize); $this->batchDeleteByPK('langlinks', ['ll_from' => $id], ['ll_from', 'll_lang'], $batchSize); $this->batchDeleteByPK('iwlinks', ['iwl_from' => $id], ['iwl_from', 'iwl_prefix', 'iwl_title'], $batchSize); // Delete any redirect entry or page props entries $dbw->delete('redirect', ['rd_from' => $id], __METHOD__); $dbw->delete('page_props', ['pp_page' => $id], __METHOD__); // Find recentchanges entries to clean up... $rcIdsForTitle = $dbw->selectFieldValues('recentchanges', 'rc_id', ['rc_type != ' . RC_LOG, 'rc_namespace' => $title->getNamespace(), 'rc_title' => $title->getDBkey(), 'rc_timestamp < ' . $dbw->addQuotes($dbw->timestamp($this->timestamp))], __METHOD__); $rcIdsForPage = $dbw->selectFieldValues('recentchanges', 'rc_id', ['rc_type != ' . RC_LOG, 'rc_cur_id' => $id], __METHOD__); // T98706: delete by PK to avoid lock contention with RC delete log insertions $rcIdBatches = array_chunk(array_merge($rcIdsForTitle, $rcIdsForPage), $batchSize); foreach ($rcIdBatches as $rcIdBatch) { $dbw->delete('recentchanges', ['rc_id' => $rcIdBatch], __METHOD__); if (count($rcIdBatches) > 1) { $lbFactory->commitAndWaitForReplication(__METHOD__, $this->ticket, ['wiki' => $dbw->getWikiID()]); } } // Commit and release the lock (if set) ScopedCallback::consume($scopedLock); }