jenkins-bot has submitted this change and it was merged.

Change subject: DB store
......................................................................


DB store

Change-Id: Ifd0bf6ee8ce468b487fd996f9dd8914120662622
---
M WikiGrok.php
A WikiGrok.sql
M includes/Hooks.php
A includes/QuestionStore.php
A maintenance/cleanupOldData.php
5 files changed, 310 insertions(+), 31 deletions(-)

Approvals:
  Kaldari: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/WikiGrok.php b/WikiGrok.php
index 6d90f78..0dc1b87 100644
--- a/WikiGrok.php
+++ b/WikiGrok.php
@@ -24,6 +24,7 @@
        'LinkedPropsCampaign' => 'campaigns/LinkedPropsCampaign',
        'QuestionCache' => 'QuestionCache',
        'QuestionPicker' => 'QuestionPicker',
+       'QuestionStore' => 'QuestionStore',
        'SimpleCampaign' => 'campaigns/SimpleCampaign',
        'WikibaseConnector' => 'WikibaseConnector',
 );
@@ -34,9 +35,10 @@
 
 $wgAPIModules['wikigrokresponse'] = 'WikiGrok\Api\ApiResponse';
 
-$wgHooks['ParserBeforeTidy'][] = 'WikiGrok\Hooks::onParserBeforeTidy';
 $wgHooks['OutputPageParserOutput'][] = 
'WikiGrok\Hooks::onOutputPageParserOutput';
 $wgHooks['LinksUpdate'][] = 'WikiGrok\Hooks::onLinksUpdate';
+$wgHooks['LoadExtensionSchemaUpdates'][] = 
'WikiGrok\Hooks::onLoadExtensionSchemaUpdates';
+$wgHooks['ArticleDeleteComplete'][] = 
'WikiGrok\Hooks::onArticleDeleteComplete';
 $wgHooks['UnitTestsList'][] = function ( &$files ) {
        $files[] = __DIR__ . '/tests/phpunit/api/ApiResponseTest.php';
 };
@@ -67,6 +69,16 @@
 //             ),
 //             'numLinks' => 10,
 //     ),
+//     'author' => array(
+//             'type' => 'Simple',
+//             'property' => 'P4'/* occupation */,
+//             'ifAll' => array(
+//                     'P7'/* instance of */ => 'Q10'/* human */,
+//                     'P4'/* occupation */ => 'Q4'/* writer */,
+//             ),
+//             'ifNotAny' => array( 'P7'/* instance of */ => 'Q11'/* author */ 
),
+//             'suggestions' => array( 'Q11'/* author */ ),
+//     ),
 );
 
 /**
diff --git a/WikiGrok.sql b/WikiGrok.sql
new file mode 100644
index 0000000..a36619d
--- /dev/null
+++ b/WikiGrok.sql
@@ -0,0 +1,42 @@
+-- Stores WikiGrok questions grouped by campaigns
+CREATE TABLE /*_*/wikigrok_questions (
+  -- Key to page_id
+  wgq_page int unsigned NOT NULL,
+  -- Campaign name, length matches page_props.pp_propname
+  wgq_campaign varchar(60) binary NOT NULL,
+  -- Serialization format version
+  wgq_version smallint default 1 NOT NULL,
+  -- Random number
+  wgq_random float NOT NULL,
+  -- Serialized campaign data
+  wgq_data blob NOT NULL,
+
+  -- Sample queries:
+  -- * SELECT ... FROM wikigrok_questions WHERE wgq_page = ... AND wgq_version 
= ...
+  -- * INSERT IGNORE INTO wikigrok_questions VALUES (...)
+  -- * DELETE FROM wikigrok_questions WHERE wgq_page = ...
+  -- * DELETE FROM wikigrok_questions WHERE wgq_page = ... AND wgq_version <> 
...
+  PRIMARY KEY(wgq_page, wgq_version, wgq_campaign)
+)/*$wgDBTableOptions*/;
+
+-- For cleaning up old campaigns
+-- Sample queries:
+-- * DELETE FROM wikigrok_questions WHERE wgq_campaign = ...
+-- * SELECT wgq_page FROM wikigrok_questions WHERE wgq_campaign = ... GROUP BY 
wgq_page
+CREATE INDEX /*i*/wgq_campaign_page ON /*_*/wikigrok_questions(wgq_campaign, 
wgq_page);
+
+-- For cleaning up old versions
+-- Sample queries:
+-- * DELETE FROM wikigrok_questions WHERE wgq_version <> ...
+-- * SELECT wgq_page FROM wikigrok_questions WHERE wgq_version = ... GROUP BY 
wgq_page
+CREATE INDEX /*i*/wgq_version_page ON /*_*/wikigrok_questions(wgq_version, 
wgq_page);
+
+-- For picking a random page
+-- Sample query:
+-- * SELECT ... FROM wikigrok_questions WHERE wgq_version = ... AND wgq_random 
> ... ORDER BY wgq_random
+CREATE INDEX /*i*/wgq_version_random ON /*_*/wikigrok_questions(wgq_version, 
wgq_random);
+
+-- For picking a random page within a campaign
+-- Sample query:
+-- * SELECT ... FROM wikigrok_questions WHERE wgq_version = ... AND 
wgq_campaign = ... AND wgq_random > ... ORDER BY wgq_random
+CREATE INDEX /*i*/wgq_version_campaign_random ON 
/*_*/wikigrok_questions(wgq_version, wgq_campaign, wgq_random);
diff --git a/includes/Hooks.php b/includes/Hooks.php
index 6b6c8af..5110979 100644
--- a/includes/Hooks.php
+++ b/includes/Hooks.php
@@ -6,31 +6,6 @@
  * Hook handlers class
  */
 class Hooks {
-       /**
-        * ParserBeforeTidy hook handler
-        * Attaches suggestions to page properties
-        * @see https://www.mediawiki.org/wiki/Manual:Hooks/ParserBeforeTidy
-        *
-        * @param \Parser $parser
-        * @return bool
-        */
-       public static function onParserBeforeTidy( \Parser &$parser ) {
-               $profileSection = new \ProfileSection( __METHOD__ );
-
-               $parserOutput = $parser->getOutput();
-               $campaigns = new QuestionPicker( self::getConfig() );
-               try {
-                       $data = $campaigns->getSlowQuestions( $parserOutput );
-                       if ( $data ) {
-                               $value = serialize( $data );
-                               $parserOutput->setProperty( 
QuestionPicker::CACHE_KEY, $value );
-                       }
-               } catch ( \Exception $ex ) {
-                       wfDebugLog( 'mobile', __METHOD__ . "(): 
{$ex->getMessage()}" );
-               }
-
-               return true;
-       }
 
        /**
         * OutputPageParserOutput hook handler
@@ -44,24 +19,26 @@
        public static function onOutputPageParserOutput( \OutputPage $out, 
\ParserOutput $parserOutput ) {
                $profileSection = new \ProfileSection( __METHOD__ );
 
+               $item = $parserOutput->getProperty( 'wikibase_item' );
                if ( !\MobileContext::singleton()->shouldDisplayMobileView()
                        || $out->getUser()->isAnon()
+                       || !$item
                ) {
                        return true;
                }
 
                try {
-                       $prop = $parserOutput->getProperty( 
QuestionPicker::CACHE_KEY );
-                       $item = $parserOutput->getProperty( 'wikibase_item' );
                        $config = self::getConfig();
                        $campaigns = new QuestionPicker( $config );
+                       $store = new QuestionStore();
 
                        if ( $config->get( 'WikiGrokDebug' ) ) {
                                $item = $out->getRequest()->getText( 
'wikidataid', $item );
                        }
 
-                       $list = self::filterCampaigns(
-                                       unserialize( $prop ),
+                       $list =
+                               self::filterCampaigns(
+                                       $store->load( $out->getTitle() ),
                                        $config->get( 'WikiGrokSlowCampaigns' )
                                )
                                + self::filterCampaigns(
@@ -80,7 +57,8 @@
 
        /**
         * LinksUpdate hook handler
-        * Purges cache (currently only fast campaigns) for the page affected
+        * 1) Gets questions from slow campaigns and saves to DB
+        * 2) Purges fast campaigns cache for the page affected
         * @see https://www.mediawiki.org/wiki/Manual:Hooks/LinksUpdate
         *
         * @param \LinksUpdate $lu
@@ -89,6 +67,16 @@
        public static function onLinksUpdate( \LinksUpdate $lu ) {
                $profileSection = new \ProfileSection( __METHOD__ );
 
+               $parserOutput = $lu->getParserOutput();
+               $campaigns = new QuestionPicker( self::getConfig() );
+               $data = array();
+               try {
+                       $data = $campaigns->getSlowQuestions( $parserOutput );
+               } catch ( \Exception $ex ) {
+                       wfDebugLog( 'mobile', __METHOD__ . "(): 
{$ex->getMessage()}" );
+               }
+               $store = new QuestionStore();
+               $store->store( $lu->getTitle(), $data );
                $cache = new QuestionCache( self::getConfig() );
                $cache->purge( $lu->getTitle()->getArticleID() );
 
@@ -96,6 +84,34 @@
        }
 
        /**
+        * ArticleDeleteComplete hook handler
+        * Deletes page information from the wikigrok_questions table
+        *
+        * @param \WikiPage $wikiPage unused
+        * @param \User $user unused
+        * @param string $reason unused
+        * @param int $id ID of page that was deleted
+        * @return bool
+        */
+       public static function onArticleDeleteComplete( $wikiPage, $user, 
$reason, $id ) {
+               $store = new QuestionStore();
+               $store->delete( $id );
+               return true;
+       }
+
+       /**
+        * LoadExtensionSchemaUpdates hook handler
+        * @see 
https://www.mediawiki.org/wiki/Manual:Hooks/LoadExtensionSchemaUpdates
+        *
+        * @param \DatabaseUpdater $updater
+        * @return bool
+        */
+       public static function onLoadExtensionSchemaUpdates( \DatabaseUpdater 
$updater ) {
+               $updater->addExtensionTable( 'wikigrok_questions', dirname( 
__DIR__ ) . '/WikiGrok.sql' );
+               return true;
+       }
+
+       /**
         * Check cached campaigns in case there are campaigns disabled by now
         *
         * @param $prop
diff --git a/includes/QuestionStore.php b/includes/QuestionStore.php
new file mode 100644
index 0000000..cb82b7a
--- /dev/null
+++ b/includes/QuestionStore.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace WikiGrok;
+
+use ProfileSection;
+use Title;
+
+/**
+ * Stores campaign questions in a permanent storage
+ */
+class QuestionStore {
+       const FORMAT_VERSION = 1;
+
+       public function store( Title $title, array $campaigns ) {
+               $profileSection = new ProfileSection( __METHOD__ );
+
+               $pageId = $title->getArticleID( Title::GAID_FOR_UPDATE );
+               if ( !$pageId ) {
+                       return;
+               }
+
+               $dbw = wfGetDB( DB_MASTER );
+               $rows = array();
+               foreach ( $campaigns as $name => $data ) {
+                       $rows[] = array(
+                               'wgq_page' => $pageId,
+                               'wgq_campaign' => $name,
+                               'wgq_version' => self::FORMAT_VERSION,
+                               'wgq_random' => mt_rand() / mt_getrandmax(),
+                               'wgq_data' => serialize( $data ),
+                       );
+               }
+               if ( $rows ) {
+                       $dbw->insert( 'wikigrok_questions', $rows, __METHOD__, 
array( 'IGNORE' ) );
+               }
+
+               $conds = array( 'wgq_page' => $pageId );
+               // Kill old versions. Uncomment when there will be any
+               /*
+               $dbw->delete( 'wikigrok_questions',
+                       $conds + array( 'wgq_version <> ' . 
self::FORMAT_VERSION ),
+                       __METHOD__
+               );*/
+               if ( $campaigns ) {
+                       $names = self::campaignList( array_keys( $campaigns ) );
+                       $conds[] = "wgq_campaign NOT IN($names)";
+               }
+               // Delete campaigns not in the new list
+               $dbw->delete( 'wikigrok_questions', $conds, __METHOD__ );
+       }
+
+       /**
+        * Loads campaign questions for the given title
+        *
+        * @param Title $title
+        * @return array Array of 'campaign name' => array( capaign data ), 
same as returned by
+        *               QuestionPicker::getQuestions()
+        */
+       public function load( Title $title ) {
+               $profileSection = new ProfileSection( __METHOD__ );
+
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( 'wikigrok_questions',
+                       array( 'wgq_campaign', 'wgq_data' ),
+                       array( 'wgq_page' => $title->getArticleID(), 
'wgq_version' => self::FORMAT_VERSION ),
+                       __METHOD__
+               );
+
+               $campaigns = array();
+               foreach ( $res as $row ) {
+                       $campaigns[$row->wgq_campaign] = unserialize( 
$row->wgq_data );
+               }
+               return $campaigns;
+       }
+
+       /**
+        * Returns a random page eligible for a given campaign
+        *
+        * @param string $campaign Campaign name. If null, a page for any 
campaign will be returned
+        * @return Title|null
+        */
+       public function getRandomPage( $campaign = null ) {
+               $profileSection = new ProfileSection( __METHOD__ );
+
+               $dbr = wfGetDB( DB_SLAVE );
+               $conds = array(
+                       'wgq_version' => self::FORMAT_VERSION,
+                       'wgq_random > ' . ( mt_rand() / mt_getrandmax() ),
+                       'page_id = wgq_page'
+               );
+               if ( $campaign ) {
+                       $conds['wgq_campaign'] = $campaign;
+               }
+               $table = $dbr->tableName( 'page' );
+               $res = $dbr->select(
+                       array( 'wikigrok_questions', 'page' ),
+                       array( 'wgq_page', "$table.*" ),
+                       $conds,
+                       __METHOD__,
+                       array(
+                               'LIMIT' => 1,
+                               'ORDER BY' => 'wgq_random',
+                       )
+               );
+               foreach ( $res as $row ) {
+                       return Title::newFromRow( $row );
+               }
+               return null;
+       }
+
+       /**
+        * Deletes all data stored for a page
+        *
+        * @param $pageId
+        */
+       public function delete( $pageId ) {
+               $profileSection = new ProfileSection( __METHOD__ );
+
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->delete( 'wikigrok_questions', array( 'wgq_page' => 
$pageId ), __METHOD__ );
+       }
+
+       /**
+        * Returns a list of campaigns escaped and concatenated into a list 
suitable for WHERE ... IN (...)
+        *
+        * @param array $campaigns
+        * @return string
+        */
+       public static function campaignList( array $campaigns ) {
+               $dbr = wfGetDB( DB_SLAVE );
+               return implode(
+                       ', ',
+                       array_map(
+                               function( $name ) use( $dbr ) {
+                                       return $dbr->addQuotes( $name );
+                               },
+                               $campaigns
+                       )
+               );
+       }
+}
diff --git a/maintenance/cleanupOldData.php b/maintenance/cleanupOldData.php
new file mode 100644
index 0000000..182a295
--- /dev/null
+++ b/maintenance/cleanupOldData.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace WikiGrok;
+use Maintenance;
+
+$IP = getenv( 'MW_INSTALL_PATH' );
+if ( $IP === false ) {
+       $IP = dirname( __FILE__ ) . '/../..';
+}
+require_once( "$IP/maintenance/Maintenance.php" );
+
+class CleanupOldData extends Maintenance {
+
+       public function __construct() {
+               $this->mDescription = 'Cleans up database from old WikiGrok 
data';
+               $this->addOption( 'old-versions', 'Removes data for previous 
storage format versions' );
+               $this->addOption( 'old-campaigns', 'Removes data for 
nonexistent campaigns' );
+               $this->mBatchSize = 500;
+       }
+       public function execute() {
+               $conds = array();
+               if ( $this->hasOption( 'old-versions' ) ) {
+                       $conds = 'wgq_version <> ' . 
QuestionStore::FORMAT_VERSION;
+                       $this->output( "Deleting data for old versions...\n" );
+                       $this->performDeletions( $conds );
+               }
+               if ( $this->hasOption( 'old-campaigns' ) ) {
+                       $config = 
\ConfigFactory::getDefaultInstance()->makeConfig( 'wikigrok' );
+                       $campaigns = array_keys( $config->get( 
'WikiGrokSlowCampaigns' ) );
+                       if ( $campaigns ) {
+                               $list = QuestionStore::campaignList( 
array_keys( $campaigns ) );
+                               $conds = "wgq_campaign NOT IN ($list)";
+                               $this->output( "Deleting data for old 
campaigns...\n" );
+                       } else {
+                               $conds = '1';
+                               $this->output( "No campaigns configured, 
deleting all data...\n" );
+                       }
+                       $this->performDeletions( $conds );
+               }
+               if ( !$conds ) {
+                       $this->error( "Error: please specify what kind of data 
to clean up!\n", 1 );
+               }
+       }
+
+       private function performDeletions( $conds ) {
+               $dbw = $this->getDB( DB_MASTER );
+               // Sigh, no abstraction for DELETE ... LIMIT ...
+               if ( $dbw->getType() != 'mysql' && $dbw->getType() != 'sqlite' 
) {
+                       $this->error( 'Only MySQL and SQLite are supported', 1 
);
+               }
+               $count = 0;
+               $table = $dbw->tableName( 'wikigrok_questions' );
+               $func = __METHOD__;
+               $sql = "DELETE /*{$func}*/ FROM {$table} WHERE {$conds} LIMIT 
{$this->mBatchSize}";
+               do {
+                       $dbw->query( $sql, __METHOD__ );
+                       $rows = $dbw->affectedRows();
+                       $count += $rows;
+                       if ( $rows ) {
+                               echo "   $count\n";
+                               wfWaitForSlaves();
+                       }
+               } while ( $rows );
+       }
+}
+
+$maintClass = 'WikiGrok\CleanupOldData';
+require_once RUN_MAINTENANCE_IF_MAIN;

-- 
To view, visit https://gerrit.wikimedia.org/r/170263
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ifd0bf6ee8ce468b487fd996f9dd8914120662622
Gerrit-PatchSet: 7
Gerrit-Project: mediawiki/extensions/WikiGrok
Gerrit-Branch: master
Gerrit-Owner: MaxSem <[email protected]>
Gerrit-Reviewer: Jdlrobson <[email protected]>
Gerrit-Reviewer: Kaldari <[email protected]>
Gerrit-Reviewer: MaxSem <[email protected]>
Gerrit-Reviewer: Phuedx <[email protected]>
Gerrit-Reviewer: Springle <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to