jenkins-bot has submitted this change and it was merged. Change subject: Store linter_cat names in a separate table ......................................................................
Store linter_cat names in a separate table linter_cat is now an int ID that points to a row in the new lint_categories table. The mapping between category names and ids is all handled in PHP, and cached in APC. Note that you will need to drop the `linter` table manually and re-run update.php for this to take effect. Change-Id: I369d9b4d8d08289b4a20d1cd29a2e327bad28ef8 --- M extension.json M includes/ApiQueryLintErrors.php A includes/CategoryManager.php M includes/Database.php M includes/Hooks.php M includes/LintErrorsPager.php M includes/SpecialLintErrors.php A lint_categories.sql M linter.sql 9 files changed, 225 insertions(+), 52 deletions(-) Approvals: Arlolra: Looks good to me, approved jenkins-bot: Verified diff --git a/extension.json b/extension.json index fbd0a03..ae8745d 100644 --- a/extension.json +++ b/extension.json @@ -8,6 +8,7 @@ "descriptionmsg": "linter-desc", "type": "specialpage", "AutoloadClasses": { + "MediaWiki\\Linter\\CategoryManager": "includes/CategoryManager.php", "MediaWiki\\Linter\\Hooks": "includes/Hooks.php", "MediaWiki\\Linter\\Database": "includes/Database.php", "MediaWiki\\Linter\\LintError": "includes/LintError.php", diff --git a/includes/ApiQueryLintErrors.php b/includes/ApiQueryLintErrors.php index b86cb99..d211dbf 100644 --- a/includes/ApiQueryLintErrors.php +++ b/includes/ApiQueryLintErrors.php @@ -33,13 +33,16 @@ public function execute() { $params = $this->extractRequestParams(); + $categoryMgr = CategoryManager::getInstance(); $this->addTables( 'linter' ); if ( $params['category'] !== null ) { - $this->addWhereFld( 'linter_cat', $params['category'] ); + $this->addWhereFld( 'linter_cat', $categoryMgr->getCategoryId( $params['category'] ) ); } else { // Limit only to enabled categories (there might be others in the DB) - $this->addWhereFld( 'linter_cat', $this->getCategories() ); + $this->addWhereFld( 'linter_cat', array_values( $categoryMgr->getCategoryIds( + $categoryMgr->getCategories() + ) ) ); } $db = $this->getDB(); if ( $params['from'] !== null ) { @@ -90,15 +93,10 @@ } } - private function getCategories() { - global $wgLinterCategories; - return array_keys( array_filter( $wgLinterCategories ) ); - } - public function getAllowedParams() { return [ 'category' => [ - ApiBase::PARAM_TYPE => $this->getCategories(), + ApiBase::PARAM_TYPE => CategoryManager::getInstance()->getCategories(), ApiBase::PARAM_ISMULTI => true, ], 'limit' => [ diff --git a/includes/CategoryManager.php b/includes/CategoryManager.php new file mode 100644 index 0000000..6045e01 --- /dev/null +++ b/includes/CategoryManager.php @@ -0,0 +1,181 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +namespace MediaWiki\Linter; + +use MediaWiki\MediaWikiServices; + +/** + * Functions for lint error categories + */ +class CategoryManager { + + /** + * @var array|bool + */ + private $map; + /** + * @var \BagOStuff + */ + private $cache; + /** + * @var string + */ + private $cacheKey; + + private function __construct() { + $this->cache = MediaWikiServices::getInstance()->getLocalServerObjectCache(); + $this->cacheKey = $this->cache->makeKey( 'linter', 'categories' ); + } + + public static function getInstance() { + static $self; + if ( !$self ) { + $self = new self(); + } + + return $self; + } + + public function getCategories() { + global $wgLinterCategories; + return array_keys( array_filter( $wgLinterCategories ) ); + } + + /** + * @see getCategoryId + * @param string $name + * @return bool|int + */ + public function getAndMaybeCreateCategoryId( $name ) { + return $this->getCategoryId( $name, true ); + } + + /** + * @param string $id + * @throws \RuntimeException + * @return int + */ + public function getCategoryName( $id ) { + if ( !$this->map ) { + $this->loadMapFromCache(); + } + + if ( $this->map ) { + $flip = array_flip( $this->map ); + if ( isset( $flip[$id] ) ) { + return $flip[$id]; + } + } + + $this->loadMapFromDB(); + $flip = array_flip( $this->map ); + if ( isset( $flip[$id] ) ) { + return $flip[$id]; + } + + throw new \RuntimeException( "Could not find name for id $id" ); + } + + public function getCategoryIds( array $names ) { + $result = []; + foreach ( $names as $name ) { + $result[$name] = $this->getCategoryId( $name ); + } + + return $result; + } + + private function loadMapFromCache() { + $this->map = $this->cache->get( $this->cacheKey ); + } + + private function saveMapToCache() { + $this->cache->set( $this->cacheKey, $this->map ); + } + + /** + * Get the int id for the category in lint_categories table + * + * @param string $name + * @param bool $create Whether to create an id if missing + * @return int|bool + */ + public function getCategoryId( $name, $create = false ) { + // Check static cache + if ( !$this->map ) { + $this->loadMapFromCache(); + } + + if ( $this->map && isset( $this->map[$name] ) ) { + return $this->map[$name]; + } + + // Cache miss, hit the DB (replica) + $this->loadMapFromDB(); + if ( isset( $this->map[$name] ) ) { + $this->saveMapToCache(); + return $this->map[$name]; + } + + if ( !$create ) { + return false; + } + + // Not in DB, create a new ID + $dbw = wfGetDB( DB_MASTER ); + $dbw->insert( + 'lint_categories', + [ 'lc_name' => $name ], + __METHOD__, + [ 'IGNORE' ] + ); + if ( $dbw->affectedRows() ) { + $id = $dbw->insertId(); + } else { + // Raced out, get the inserted id + $id = $dbw->selectField( + 'lint_categories', + 'lc_id', + [ 'lc_name' => $name ], + __METHOD__, + [ 'LOCK IN SHARE MODE' ] + ); + } + + $this->map[$name] = (int)$id; + $this->saveMapToCache(); + return $this->map[$name]; + } + + private function loadMapFromDB() { + $rows = wfGetDB( DB_REPLICA )->select( + 'lint_categories', + [ 'lc_id', 'lc_name' ], + [], + __METHOD__ + ); + $this->map = []; + foreach ( $rows as $row ) { + $this->map[$row->lc_name] = (int)$row->lc_id; + } + } + +} diff --git a/includes/Database.php b/includes/Database.php index 38abc0a..64fe85a 100644 --- a/includes/Database.php +++ b/includes/Database.php @@ -20,14 +20,12 @@ namespace MediaWiki\Linter; -use DBAccessObjectUtils; -use IDBAccessObject; use FormatJson; /** * Database logic */ -class Database implements IDBAccessObject { +class Database { /** * @var int */ @@ -44,17 +42,14 @@ * Get a specific LintError by id * * @param int $id linter_id - * @param int $flags * @return bool|LintError */ - public function getFromId( $id, $flags = 0 ) { - list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags ); - $row = wfGetDB( $index )->selectRow( + public function getFromId( $id ) { + $row = wfGetDB( DB_REPLICA )->selectRow( 'linter', [ 'linter_cat', 'linter_params' ], [ 'linter_id' => $id, 'linter_page' => $this->pageId ], - __METHOD__, - $options + __METHOD__ ); if ( $row ) { @@ -72,8 +67,9 @@ * @return LintError */ public static function makeLintError( $row ) { + $categoryMgr = CategoryManager::getInstance(); return new LintError( - $row->linter_cat, + $categoryMgr->getCategoryName( $row->linter_cat ), $row->linter_params, (int)$row->linter_id ); @@ -82,17 +78,14 @@ /** * Get all the lint errors for a page * - * @param int $flags * @return LintError[] */ - public function getForPage( $flags = 0 ) { - list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags ); - $rows = wfGetDB( $index )->select( + public function getForPage() { + $rows = wfGetDB( DB_REPLICA )->select( 'linter', [ 'linter_id', 'linter_cat', 'linter_params' ], [ 'linter_page' => $this->pageId ], - __METHOD__, - $options + __METHOD__ ); $result = []; foreach ( $rows as $row ) { @@ -111,17 +104,12 @@ * @return array */ private function serializeError( LintError $error ) { - if ( $error->lintId !== 0 ) { - return [ - 'linter_id' => $error->lintId, - ]; - } else { - return [ - 'linter_page' => $this->pageId, - 'linter_cat' => $error->category, - 'linter_params' => FormatJson::encode( $error->params, false, FormatJson::ALL_OK ), - ]; - } + $categoryMgr = CategoryManager::getInstance(); + return [ + 'linter_page' => $this->pageId, + 'linter_cat' => $categoryMgr->getAndMaybeCreateCategoryId( $error->category ), + 'linter_params' => FormatJson::encode( $error->params, false, FormatJson::ALL_OK ), + ]; } /** @@ -132,7 +120,7 @@ * @return array [ 'deleted' => int|bool, 'added' => int ] */ public function setForPage( $errors ) { - $previous = $this->getForPage( self::READ_LATEST ); + $previous = $this->getForPage(); $dbw = wfGetDB( DB_MASTER ); if ( !$previous && !$errors ) { return [ 'deleted' => 0, 'added' => 0 ]; diff --git a/includes/Hooks.php b/includes/Hooks.php index 74e69c3..dd7c303 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -28,9 +28,9 @@ * @param DatabaseUpdater $updater */ public static function onLoadExtensionSchemaUpdates( DatabaseUpdater $updater ) { - $updater->addExtensionTable( - 'linter', dirname( __DIR__ ) . '/linter.sql' - ); + $dir = dirname( __DIR__ ); + $updater->addExtensionTable( 'linter', "$dir/linter.sql" ); + $updater->addExtensionTable( 'lint_categories', "$dir/lint_categories.sql" ); } /** diff --git a/includes/LintErrorsPager.php b/includes/LintErrorsPager.php index fc54126..56abe1c 100644 --- a/includes/LintErrorsPager.php +++ b/includes/LintErrorsPager.php @@ -33,6 +33,9 @@ class LintErrorsPager extends TablePager { private $category; + + private $categoryId; + /** * @var LinkRenderer */ @@ -40,6 +43,7 @@ public function __construct( IContextSource $context, $category, LinkRenderer $linkRenderer ) { $this->category = $category; + $this->categoryId = CategoryManager::getInstance()->getCategoryId( $this->category ); $this->linkRenderer = $linkRenderer; parent::__construct( $context ); } @@ -54,7 +58,7 @@ 'linter_id', 'linter_params', ] ), - 'conds' => [ 'linter_cat' => $this->category ], + 'conds' => [ 'linter_cat' => $this->categoryId ], 'join_conds' => [ 'page' => [ 'INNER JOIN', 'page_id=linter_page' ] ] ]; } @@ -73,7 +77,7 @@ public function formatValue( $name, $value ) { $row = $this->mCurrentRow; - $row->linter_cat = $this->category; + $row->linter_cat = $this->categoryId; $lintError = Database::makeLintError( $row ); switch ( $name ) { case 'title': diff --git a/includes/SpecialLintErrors.php b/includes/SpecialLintErrors.php index c46d4e7..6dd9324 100644 --- a/includes/SpecialLintErrors.php +++ b/includes/SpecialLintErrors.php @@ -31,18 +31,10 @@ parent::__construct( 'LintErrors' ); } - /** - * @return array - */ - private function getCategories() { - global $wgLinterCategories; - return array_keys( array_filter( $wgLinterCategories ) ); - } - public function execute( $par ) { $this->setHeaders(); $this->outputHeader(); - $cats = $this->getCategories(); + $cats = CategoryManager::getInstance()->getCategories(); if ( in_array( $par, $cats ) ) { $this->category = $par; } diff --git a/lint_categories.sql b/lint_categories.sql new file mode 100644 index 0000000..21b60ee --- /dev/null +++ b/lint_categories.sql @@ -0,0 +1,9 @@ +CREATE TABLE /*_*/lint_categories ( + -- primary key + lc_id int UNSIGNED PRIMARY KEY not null AUTO_INCREMENT, + -- category name + lc_name VARCHAR(30) not null +) /*$wgDBTableOptions*/; + +-- Query by name +CREATE UNIQUE INDEX /*i*/lc_name ON /*_*/lint_categories(lc_name); diff --git a/linter.sql b/linter.sql index 8c4484c..3205806 100644 --- a/linter.sql +++ b/linter.sql @@ -3,8 +3,8 @@ linter_id int UNSIGNED PRIMARY KEY not null AUTO_INCREMENT, -- page id linter_page int UNSIGNED not null, - -- error category - linter_cat VARCHAR(30) not null, + -- error category (lint_categories.lc_id) + linter_cat int UNSIGNED not null, -- extra parameters about the error, JSON encoded linter_params blob NOT NULL ) /*$wgDBTableOptions*/; -- To view, visit https://gerrit.wikimedia.org/r/318660 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I369d9b4d8d08289b4a20d1cd29a2e327bad28ef8 Gerrit-PatchSet: 3 Gerrit-Project: mediawiki/extensions/Linter Gerrit-Branch: master Gerrit-Owner: Legoktm <legoktm.wikipe...@gmail.com> Gerrit-Reviewer: Arlolra <abrea...@wikimedia.org> Gerrit-Reviewer: Legoktm <legoktm.wikipe...@gmail.com> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits