Gergő Tisza has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/392748 )

Change subject: Deduplicate projects into their own table
......................................................................

Deduplicate projects into their own table

Create a reading_list_project table for projects (domains).
Use it to normalize the project column and not waste space e.g.
storing 'en.wikipedia.org' a million times; also to validate
projects (the table needs to be pregenerated, projects that are
not found in it are rejected).

The extension is agnostic about what values are supposed to be
used as projects, but a maintenance script is provided which
puts the wikifarm's wgCanonicalServer values (things like
'https://en.wikipedia.org') into the new table.

Change-Id: I047d1493f1d9f51d733c1925a38c080829589f35
---
M i18n/en.json
M i18n/qqq.json
A maintenance/populateProjectsFromSiteMatrix.php
A sql/patches/02-add-reading_list_project.sql
M sql/readinglists.sql
M src/Api/ApiQueryReadingListEntries.php
M src/Api/ApiReadingListsCreateEntry.php
M src/Doc/ReadingListEntryRow.php
M src/HookHandler.php
M src/ReadingListRepository.php
M tests/src/ReadingListRepositoryTest.php
11 files changed, 277 insertions(+), 61 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ReadingLists 
refs/changes/48/392748/1

diff --git a/i18n/en.json b/i18n/en.json
index ee02621..d66ee58 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -21,6 +21,7 @@
        "readinglists-db-error-list-limit": "Users cannot have more than $1 
{{PLURAL:$1|list|lists}}.",
        "readinglists-db-error-entry-limit": "List $1 cannot have more than $2 
{{PLURAL:$2|entry|entries}}.",
        "readinglists-db-error-too-long": "Value for field $1 cannot be longer 
than $2 bytes.",
+       "readinglists-db-error-no-such-project": "'$1' is not a recognized 
project.",
        "readinglists-apierror-project-title-param": "<var>$1project</var> and 
<var>$1title</var> must be used together.",
        "readinglists-apierror-too-old": "Timestamps passed to 
<var>$1changedsince</var> cannot be older than <kbd>$2</kbd>.",
        "apihelp-query+readinglists-summary": "List or filter the user's 
reading lists and show metadata about them.",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 5334ebb..d4ed940 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -22,6 +22,7 @@
        "readinglists-db-error-list-limit": "Error message used when the user 
has as many or more lists than the maximum allowed, and tries to add another 
one.\n\nParameters:\n* $1 - the maximum allowed number of lists per user.",
        "readinglists-db-error-entry-limit": "Error message used when the user 
has as many or more entries in the given list than the maximum allowed, and 
tries to add another one.\n\nParameters:\n* $1 - the ID of the list in 
question.\n$2 - the maximum allowed number of entries per list.",
        "readinglists-db-error-too-long": "Error message used when the user 
tries to set list/entry string fields to a longer value than what the database 
schema allows.\n\nParameters:\n* $1 - DB field name.\n$2 - DB field maximum 
length (in bytes).",
+       "readinglists-db-error-no-such-project": "Error message used when the 
user tries to add a new list entry, but the project does not match any of the 
known ones.\n\nParameters:\n* $1 - the project.",
        "readinglists-apierror-project-title-param": "{{doc-apierror}}\n$1 is 
the module prefix.",
        "readinglists-apierror-too-old": "{{doc-apierror}}\n$1 is the module 
prefix, $2 is the expiry date for deleted lists/entries.",
        "apihelp-query+readinglists-summary": 
"{{doc-apihelp-summary|query+readinglists}}",
diff --git a/maintenance/populateProjectsFromSiteMatrix.php 
b/maintenance/populateProjectsFromSiteMatrix.php
new file mode 100644
index 0000000..0a5fb0e
--- /dev/null
+++ b/maintenance/populateProjectsFromSiteMatrix.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace MediaWiki\Extensions\ReadingLists\Maintenance;
+
+use Generator;
+use Maintenance;
+use MediaWiki\Extensions\ReadingLists\Utils;
+use MediaWiki\MediaWikiServices;
+use SiteMatrix;
+
+require_once getenv( 'MW_INSTALL_PATH' ) !== false
+       ? getenv( 'MW_INSTALL_PATH' ) . '/maintenance/Maintenance.php'
+       : __DIR__ . '/../../../maintenance/Maintenance.php';
+
+/**
+ * Maintenance script for populating the reading_list_project table.
+ * If the table is already populated, add new entries (old entries that don't 
exist anymore are
+ * left in place).
+ * Uses the SiteMatrix extension as the data source.
+ * @ingroup Maintenance
+ */
+class PopulateProjectsFromSiteMatrix extends Maintenance {
+
+       /** @var SiteMatrix */
+       private $siteMatrix;
+
+       public function __construct() {
+               parent::__construct();
+               $this->addDescription(
+                       'Populate (or update) the reading_list_project table 
from SiteMatrix data.' );
+               $this->setBatchSize( 100 );
+               $this->requireExtension( 'SiteMatrix' );
+               $this->siteMatrix = new SiteMatrix();
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function execute() {
+               $services = MediaWikiServices::getInstance();
+               $loadBalancerFactory = $services->getDBLoadBalancerFactory();
+               $dbw = Utils::getDB( DB_MASTER, $services );
+               $inserted = 0;
+
+               $this->output( "populating...\n" );
+               foreach ( $this->generateAllowedDomains() as list( $project ) ) 
{
+                       $dbw->insert(
+                               'reading_list_project',
+                               [ 'rlp_project' => $project ],
+                               __METHOD__,
+                               [ 'IGNORE' ]
+                       );
+                       if ( $dbw->affectedRows() ) {
+                               $inserted++;
+                               if ( $inserted % $this->mBatchSize ) {
+                                       
$loadBalancerFactory->waitForReplication();
+                               }
+                       }
+               }
+               $this->output( "inserted $inserted projects\n" );
+       }
+
+       /**
+        * List all sites known to SiteMatrix.
+        * @return Generator [ language, site ]
+        */
+       private function generateSites() {
+               foreach ( $this->siteMatrix->getSites() as $site ) {
+                       foreach ( $this->siteMatrix->getLangList() as $lang ) {
+                               if ( !$this->siteMatrix->exist( $lang, $site ) 
) {
+                                       continue;
+                               }
+                               yield [ $lang, $site ];
+                       }
+               }
+               foreach ( $this->siteMatrix->getSpecials() as $special ) {
+                       list( $lang, $site ) = $special;
+                       yield [ $lang, $site ];
+               }
+       }
+
+       /**
+        * List all sites that are safe to add to the reading list.
+        * @return Generator [ domain, dbname ]
+        */
+       private function generateAllowedDomains() {
+               foreach ( $this->generateSites() as list( $lang, $site ) ) {
+                       $dbName = $this->siteMatrix->getDBName( $lang, $site );
+                       $domain = $this->siteMatrix->getCanonicalUrl( $lang, 
$site );
+
+                       if ( $this->siteMatrix->isPrivate( $dbName ) ) {
+                               continue;
+                       }
+
+                       yield [ $domain, $dbName ];
+               }
+       }
+
+}
+
+$maintClass = PopulateProjectsFromSiteMatrix::class;
+require_once RUN_MAINTENANCE_IF_MAIN;
diff --git a/sql/patches/02-add-reading_list_project.sql 
b/sql/patches/02-add-reading_list_project.sql
new file mode 100644
index 0000000..35fbd15
--- /dev/null
+++ b/sql/patches/02-add-reading_list_project.sql
@@ -0,0 +1,31 @@
+-- Table for storing projects (domains) efficiently.
+CREATE TABLE /*_*/reading_list_project (
+    rlp_id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+    -- Wiki project domain.
+    rlp_project VARBINARY(255) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/rlp_project ON /*_*/reading_list_project 
(rlp_project);
+
+-- migration step 1: create new column, drop old indexes (can't create new 
ones yet b/c uniqueness)
+ALTER TABLE reading_list_entry
+    -- Reference to reading_list_project.rlp_id.
+    ADD COLUMN rle_rlp_id INTEGER UNSIGNED NOT NULL;
+DROP INDEX /*i*/rle_user_project_title ON /*_*/reading_list_entry;
+DROP INDEX /*i*/rle_list_project_title ON /*_*/reading_list_entry;
+
+-- migration step 2: copy old column data to new table, write ids into new 
column
+-- add new indexes as we are uniqe now
+INSERT IGNORE INTO reading_list_project (rlp_project)
+    SELECT rle_project FROM reading_list_entry;
+UPDATE reading_list_entry
+    JOIN reading_list_project ON rle_project = rlp_project
+    SET rle_rlp_id = rlp_id;
+-- For getting all lists of a given user which contain a specified page.
+CREATE INDEX /*i*/rle_user_project_title ON /*_*/reading_list_entry 
(rle_user_id, rle_rlp_id, rle_title);
+-- For ensuring there are no duplicate pages on a single list.
+CREATE UNIQUE INDEX /*i*/rle_list_project_title ON /*_*/reading_list_entry 
(rle_rl_id, rle_rlp_id, rle_title);
+
+-- migration step 3: drop old data
+ALTER TABLE reading_list_entry
+    DROP COLUMN rle_project;
+
diff --git a/sql/readinglists.sql b/sql/readinglists.sql
index 29d125f..c860397 100644
--- a/sql/readinglists.sql
+++ b/sql/readinglists.sql
@@ -42,8 +42,8 @@
     rle_rl_id INTEGER UNSIGNED NOT NULL,
     -- Central ID of user, denormalized for the benefit of the /pages/ route.
     rle_user_id INTEGER UNSIGNED NOT NULL,
-    -- Wiki project domain.
-    rle_project VARCHAR(255) BINARY NOT NULL,
+    -- Reference to reading_list_project.rlp_id.
+    rle_rlp_id INTEGER UNSIGNED NOT NULL,
     -- Page title.
     -- We can't easily use page ids due to the cross-wiki nature of the 
project;
     -- also, page ids don't age well when content is deleted/moved.
@@ -59,14 +59,14 @@
 -- For getting all entries in a list and for syncing list entries that changed 
since a given date.
 CREATE INDEX /*i*/rle_list_updated ON /*_*/reading_list_entry (rle_rl_id, 
rle_date_updated);
 -- For getting all lists of a given user which contain a specified page.
-CREATE INDEX /*i*/rle_user_project_title ON /*_*/reading_list_entry 
(rle_user_id, rle_project, rle_title);
+CREATE INDEX /*i*/rle_user_project_title ON /*_*/reading_list_entry 
(rle_user_id, rle_rlp_id, rle_title);
 -- For ensuring there are no duplicate pages on a single list.
-CREATE UNIQUE INDEX /*i*/rle_list_project_title ON /*_*/reading_list_entry 
(rle_rl_id, rle_project, rle_title);
+CREATE UNIQUE INDEX /*i*/rle_list_project_title ON /*_*/reading_list_entry 
(rle_rl_id, rle_rlp_id, rle_title);
 
--- TODO use lookup table to deduplicate domains
--- -- Table for storing domains efficiently.
--- CREATE TABLE /*_*/reading_list_domain (
---     rld_id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
---     rld_domain VARBINARY(255) NOT NULL
--- ) /*$wgDBTableOptions*/;
--- CREATE UNIQUE INDEX /*i*/rld_domain ON /*_*/reading_list_domain 
(rld_domain);
+-- Table for storing projects (domains) efficiently.
+CREATE TABLE /*_*/reading_list_project (
+    rlp_id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+    -- Wiki project domain.
+    rlp_project VARCHAR(255) BINARY NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/rlp_project ON /*_*/reading_list_project 
(rlp_project);
diff --git a/src/Api/ApiQueryReadingListEntries.php 
b/src/Api/ApiQueryReadingListEntries.php
index acc9b77..eadac97 100644
--- a/src/Api/ApiQueryReadingListEntries.php
+++ b/src/Api/ApiQueryReadingListEntries.php
@@ -200,7 +200,7 @@
                return [
                        'id' => (int)$row->rle_id,
                        'listId' => (int)$row->rle_rl_id,
-                       'project' => $row->rle_project,
+                       'project' => $row->rlp_project,
                        'title' => $row->rle_title,
                        'created' => wfTimestamp( TS_ISO_8601, 
$row->rle_date_created ),
                        'updated' => wfTimestamp( TS_ISO_8601, 
$row->rle_date_updated ),
@@ -213,7 +213,7 @@
         * @return Title|string
         */
        private function getResultTitle( $row ) {
-               $interwikiPrefix = $this->getReverseInterwikiLookup()->lookup( 
$row->rle_project );
+               $interwikiPrefix = $this->getReverseInterwikiLookup()->lookup( 
$row->rlp_project );
                if ( is_string( $interwikiPrefix ) ) {
                        if ( $interwikiPrefix === '' ) {
                                $title = Title::newFromText( $row->rle_title );
@@ -236,7 +236,7 @@
                // For lack of a better option let's create an invalid title.
                // ApiPageSet::populateFromTitles() is not documented to accept 
strings
                // but it will actually work.
-               return 'Invalid project|' . $row->rle_project . '|' . 
$row->rle_title;
+               return 'Invalid project|' . $row->rlp_project . '|' . 
$row->rle_title;
        }
 
 }
diff --git a/src/Api/ApiReadingListsCreateEntry.php 
b/src/Api/ApiReadingListsCreateEntry.php
index 8221f97..d476c42 100644
--- a/src/Api/ApiReadingListsCreateEntry.php
+++ b/src/Api/ApiReadingListsCreateEntry.php
@@ -56,7 +56,7 @@
                        'project' => [
                                self::PARAM_TYPE => 'string',
                                self::PARAM_REQUIRED => true,
-                               self::PARAM_MAX_BYTES => 
ReadingListRepository::$fieldLength['rle_project'],
+                               self::PARAM_MAX_BYTES => 
ReadingListRepository::$fieldLength['rlp_project'],
                        ],
                        'title' => [
                                self::PARAM_TYPE => 'string',
diff --git a/src/Doc/ReadingListEntryRow.php b/src/Doc/ReadingListEntryRow.php
index edace03..277c5e9 100644
--- a/src/Doc/ReadingListEntryRow.php
+++ b/src/Doc/ReadingListEntryRow.php
@@ -22,8 +22,15 @@
        /** @var string Central ID of user. */
        public $rle_user_id;
 
-       /** @var string Wiki project domain. */
-       public $rle_project;
+       /** @var int Reference to reading_list_project.rlp_id. */
+       public $rle_rlp_id;
+
+       /**
+        * Wiki project domain.
+        * Only present when joined with reading_list_project (used for 
deduplicated storage).
+        * @var string
+        */
+       public $rlp_project;
 
        /**
         * Page title.
diff --git a/src/HookHandler.php b/src/HookHandler.php
index b0cc0aa..f8333b8 100644
--- a/src/HookHandler.php
+++ b/src/HookHandler.php
@@ -16,6 +16,7 @@
        public static $testTables = [
                'reading_list',
                'reading_list_entry',
+               'reading_list_project',
        ];
 
        /**
@@ -44,6 +45,8 @@
                        $patchDir = "$baseDir/sql/patches";
                        $updater->addExtensionTable( 'reading_list', 
"$baseDir/sql/readinglists.sql" );
                        $updater->dropExtensionTable( 'reading_list_sortkey', 
"$patchDir/01-drop-sortkeys.sql" );
+                       $updater->addExtensionTable( 'reading_list_project',
+                               "$patchDir/02-add-reading_list_project.sql" );
                }
                return true;
        }
diff --git a/src/ReadingListRepository.php b/src/ReadingListRepository.php
index 794d16c..ed4351e 100644
--- a/src/ReadingListRepository.php
+++ b/src/ReadingListRepository.php
@@ -10,6 +10,7 @@
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerInterface;
 use Psr\Log\NullLogger;
+use Wikimedia\Rdbms\FakeResultWrapper;
 use Wikimedia\Rdbms\IDatabase;
 // @codingStandardsIgnoreStart MediaWiki.Classes.UnusedUseStatement.UnusedUse
 use Wikimedia\Rdbms\IResultWrapper;
@@ -39,7 +40,7 @@
                'rl_color' => 6,
                'rl_image' => 255,
                'rl_icon' => 32,
-               'rle_project' => 255,
+               'rlp_project' => 255,
                'rle_title' => 255,
        ];
 
@@ -357,7 +358,7 @@
         */
        public function addListEntry( $id, $project, $title ) {
                $this->assertUser();
-               $this->assertFieldLength( 'rle_project', $project );
+               $this->assertFieldLength( 'rlp_project', $project );
                $this->assertFieldLength( 'rle_title', $title );
                $this->selectValidList( $id, self::READ_LOCKING );
                if ( $this->entryLimit && $this->getEntryCount( $id, 
self::READ_LATEST ) >= $this->entryLimit ) {
@@ -365,15 +366,21 @@
                                [ $id, $this->entryLimit ] );
                }
 
+               $projectId = $this->getProjectId( $project );
+               if ( !$projectId ) {
+                       throw new ReadingListRepositoryException( 
'readinglists-db-error-no-such-project',
+                               [ $project ] );
+               }
+
                // due to the combination of soft deletion + unique constraint 
on
-               // rle_rl_id + rle_project + rle_title, recreation needs 
special handling
+               // rle_rl_id + rle_rlp_id + rle_title, recreation needs special 
handling
                /** @var ReadingListEntryRow $row */
                $row = $this->dbw->selectRow(
                        'reading_list_entry',
                        [ 'rle_id', 'rle_deleted' ],
                        [
                                'rle_rl_id' => $id,
-                               'rle_project' => $project,
+                               'rle_rlp_id' => $projectId,
                                'rle_title' => $title,
                        ],
                        __METHOD__,
@@ -386,7 +393,7 @@
                                [
                                        'rle_rl_id' => $id,
                                        'rle_user_id' => $this->userId,
-                                       'rle_project' => $project,
+                                       'rle_rlp_id' => $projectId,
                                        'rle_title' => $title,
                                        'rle_date_created' => 
$this->dbw->timestamp(),
                                        'rle_date_updated' => 
$this->dbw->timestamp(),
@@ -460,9 +467,10 @@
                }
 
                $res = $this->dbr->select(
-                       'reading_list_entry',
+                       [ 'reading_list_entry', 'reading_list_project' ],
                        $this->getListEntryFields(),
                        [
+                               'rle_rlp_id = rlp_id',
                                'rle_rl_id' => $ids,
                                'rle_user_id' => $this->userId,
                                'rle_deleted' => 0,
@@ -579,10 +587,11 @@
        public function getListEntriesByDateUpdated( $date, $limit = 1000, 
$offset = 0 ) {
                $this->assertUser();
                $res = $this->dbr->select(
-                       [ 'reading_list', 'reading_list_entry' ],
+                       [ 'reading_list', 'reading_list_entry', 
'reading_list_project' ],
                        $this->getListEntryFields(),
                        [
                                'rl_id = rle_rl_id',
+                               'rle_rlp_id = rlp_id',
                                'rl_user_id' => $this->userId,
                                'rl_deleted' => 0,
                                'rle_date_updated > ' . $this->dbr->addQuotes( 
$this->dbr->timestamp( $date ) ),
@@ -668,13 +677,18 @@
         */
        public function getListsByPage( $project, $title, $limit = 1000, 
$offset = 0 ) {
                $this->assertUser();
+               $projectId = $this->getProjectId( $project );
+               if ( !$projectId ) {
+                       return new FakeResultWrapper( [] );
+               }
+
                $res = $this->dbr->select(
                        [ 'reading_list', 'reading_list_entry' ],
                        $this->getListFields(),
                        [
                                'rl_id = rle_rl_id',
                                'rl_user_id' => $this->userId,
-                               'rle_project' => $project,
+                               'rle_rlp_id' => $projectId,
                                'rle_title' => $title,
                                'rl_deleted' => 0,
                                'rle_deleted' => 0,
@@ -722,6 +736,7 @@
 
        /**
         * Get this list of reading_list_entry fields that normally need to be 
selected.
+        * Can only be used with queries that join on reading_list_project.
         * @return array
         */
        private function getListEntryFields() {
@@ -729,7 +744,8 @@
                        'rle_id',
                        'rle_rl_id',
                        // returning rle_user_id is pointless as lists are only 
available to the owner
-                       'rle_project',
+                       // skip rle_rlp_id, it's only needed for the join
+                       'rlp_project',
                        'rle_title',
                        'rle_date_created',
                        'rle_date_updated',
@@ -814,6 +830,20 @@
        }
 
        /**
+        * Look up a project ID.
+        * @param string $project
+        * @return int|null
+        */
+       private function getProjectId( $project ) {
+               $id = $this->dbr->selectField(
+                       'reading_list_project',
+                       'rlp_id',
+                       [ 'rlp_project' => $project ]
+               );
+               return $id === false ? null : (int)$id;
+       }
+
+       /**
         * Returns the number of (non-deleted) list entries of the given list.
         * Verifying that the list is valid is caller's responsibility.
         * @param int $id List id
diff --git a/tests/src/ReadingListRepositoryTest.php 
b/tests/src/ReadingListRepositoryTest.php
index dbaed2b..94a968d 100644
--- a/tests/src/ReadingListRepositoryTest.php
+++ b/tests/src/ReadingListRepositoryTest.php
@@ -408,6 +408,7 @@
        public function testAddListEntry() {
                $repository = new ReadingListRepository( 1, $this->db, 
$this->db, $this->lbFactory );
                $repository->setupForUser();
+               list( $projectId ) = $this->addProjects( [ 'en.wikipedia.org' ] 
);
                list( $listId, $deletedListId ) = $this->addLists( 1, [
                        [
                                'rl_name' => 'foo',
@@ -423,7 +424,7 @@
                /** @var ReadingListEntryRow $row */
                $row = $this->db->selectRow( 'reading_list_entry', '*', [ 
'rle_id' => $entryId ] );
                $this->assertEquals( 1, $row->rle_user_id );
-               $this->assertEquals( 'en.wikipedia.org', $row->rle_project );
+               $this->assertEquals( $projectId, $row->rle_rlp_id );
                $this->assertEquals( 'Foo', $row->rle_title );
                $this->assertTimestampEquals( wfTimestampNow(), 
$row->rle_date_created );
                $this->assertTimestampEquals( wfTimestampNow(), 
$row->rle_date_updated );
@@ -435,7 +436,7 @@
                /** @var ReadingListEntryRow $row */
                $row = $this->db->selectRow( 'reading_list_entry', '*', [ 
'rle_id' => $entryId2 ] );
                $this->assertEquals( 1, $row->rle_user_id );
-               $this->assertEquals( 'en.wikipedia.org', $row->rle_project );
+               $this->assertEquals( $projectId, $row->rle_rlp_id );
                $this->assertEquals( 'Foo', $row->rle_title );
                $this->assertTimestampEquals( wfTimestampNow(), 
$row->rle_date_created );
                $this->assertTimestampEquals( wfTimestampNow(), 
$row->rle_date_updated );
@@ -462,6 +463,11 @@
                                $repository->addListEntry( $listId, 
'en.wikipedia.org', 'Foo' );
                        }
                );
+               $this->assertFailsWith( 'readinglists-db-error-no-such-project',
+                       function () use ( $repository, $listId ) {
+                               $repository->addListEntry( $listId, 
'nosuch.project.org', 'Foo' );
+                       }
+               );
        }
 
        // @codingStandardsIgnoreLine 
MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
@@ -470,6 +476,7 @@
                $repository->setLimits( null, 1 );
                $repository->setupForUser();
 
+               $this->addProjects( [ 'en.wikipedia.org' ] );
                list( $listId ) = $this->addLists( 1, [
                        [
                                'rl_name' => 'foo',
@@ -494,7 +501,7 @@
                $this->addListEntries( $defaultId, 1, [
                        [
                                'rle_user_id' => 1,
-                               'rle_project' => 'foo',
+                               'rlp_project' => 'foo',
                                'rle_title' => 'Foo',
                                'rle_date_created' => wfTimestampNow(),
                                'rle_date_updated' => wfTimestampNow(),
@@ -507,28 +514,28 @@
                                'rl_name' => 'test',
                                'entries' => [
                                        [
-                                               'rle_project' => 'foo',
+                                               'rlp_project' => 'foo',
                                                'rle_title' => 'bar',
                                                'rle_date_created' => 
wfTimestampNow(),
                                                'rle_date_updated' => 
wfTimestampNow(),
                                                'rle_deleted' => 0,
                                        ],
                                        [
-                                               'rle_project' => 'foo2',
+                                               'rlp_project' => 'foo2',
                                                'rle_title' => 'bar2',
                                                'rle_date_created' => 
'20100101000000',
                                                'rle_date_updated' => 
'20120101000000',
                                                'rle_deleted' => 0,
                                        ],
                                        [
-                                               'rle_project' => 'foo3',
+                                               'rlp_project' => 'foo3',
                                                'rle_title' => 'bar3',
                                                'rle_date_created' => 
wfTimestampNow(),
                                                'rle_date_updated' => 
wfTimestampNow(),
                                                'rle_deleted' => 0,
                                        ],
                                        [
-                                               'rle_project' => 'foo4',
+                                               'rlp_project' => 'foo4',
                                                'rle_title' => 'bar4',
                                                'rle_date_created' => 
wfTimestampNow(),
                                                'rle_date_updated' => 
wfTimestampNow(),
@@ -548,8 +555,8 @@
                        $this->assertTimestampEquals( 
$expected['rle_date_updated'], $actual['rle_date_updated'],
                                "Mismatch in item $n" );
                        unset( $expected['rle_date_created'], 
$expected['rle_date_updated'] );
-                       unset( $actual['rle_id'],  $actual['rle_date_created'],
-                               $actual['rle_date_updated'] );
+                       unset( $actual['rle_id'], $actual['rle_rlp_id'],
+                               $actual['rle_date_created'], 
$actual['rle_date_updated'] );
                        $this->assertArrayEquals( $expected, $actual, false, 
true );
                };
                $compare = function ( $expected, $res ) use ( 
$compareResultItems ) {
@@ -562,7 +569,7 @@
                $expectedData = [
                        [
                                'rle_rl_id' => $defaultId,
-                               'rle_project' => 'foo',
+                               'rlp_project' => 'foo',
                                'rle_title' => 'Foo',
                                'rle_date_created' => wfTimestampNow(),
                                'rle_date_updated' => wfTimestampNow(),
@@ -570,7 +577,7 @@
                        ],
                        [
                                'rle_rl_id' => $listId,
-                               'rle_project' => 'foo',
+                               'rlp_project' => 'foo',
                                'rle_title' => 'bar',
                                'rle_date_created' => wfTimestampNow(),
                                'rle_date_updated' => wfTimestampNow(),
@@ -578,7 +585,7 @@
                        ],
                        [
                                'rle_rl_id' => $listId,
-                               'rle_project' => 'foo2',
+                               'rlp_project' => 'foo2',
                                'rle_title' => 'bar2',
                                'rle_date_created' => '20100101000000',
                                'rle_date_updated' => '20120101000000',
@@ -586,7 +593,7 @@
                        ],
                        [
                                'rle_rl_id' => $listId,
-                               'rle_project' => 'foo3',
+                               'rlp_project' => 'foo3',
                                'rle_title' => 'bar3',
                                'rle_date_created' => wfTimestampNow(),
                                'rle_date_updated' => wfTimestampNow(),
@@ -630,6 +637,7 @@
        public function testDeleteListEntry() {
                $repository = new ReadingListRepository( 1, $this->db, 
$this->db, $this->lbFactory );
                $repository->setupForUser();
+               list( $fooProjectId ) = $this->addProjects( [ 'foo' ] );
                list( $listId, $deletedListId ) = $this->addLists( 1, [
                        [
                                'rl_is_default' => 0,
@@ -645,21 +653,21 @@
                ] );
                list( $fooId, $foo2Id, $deletedId ) = $this->addListEntries( 
$listId, 1, [
                        [
-                               'rle_project' => 'foo',
+                               'rlp_project' => 'foo',
                                'rle_title' => 'bar',
                                'rle_date_created' => wfTimestampNow(),
                                'rle_date_updated' => wfTimestampNow(),
                                'rle_deleted' => 0,
                        ],
                        [
-                               'rle_project' => 'foo2',
+                               'rlp_project' => 'foo2',
                                'rle_title' => 'bar2',
                                'rle_date_created' => wfTimestampNow(),
                                'rle_date_updated' => wfTimestampNow(),
                                'rle_deleted' => 0,
                        ],
                        [
-                               'rle_project' => 'foo3',
+                               'rlp_project' => 'foo3',
                                'rle_title' => 'bar3',
                                'rle_date_created' => wfTimestampNow(),
                                'rle_date_updated' => wfTimestampNow(),
@@ -668,7 +676,7 @@
                ] );
                list( $parentDeletedId ) = $this->addListEntries( 
$deletedListId, 1, [
                        [
-                               'rle_project' => 'foo4',
+                               'rlp_project' => 'foo4',
                                'rle_title' => 'bar4',
                                'rle_date_created' => wfTimestampNow(),
                                'rle_date_updated' => wfTimestampNow(),
@@ -682,7 +690,7 @@
                /** @var ReadingListEntryRow $row */
                $row = $this->db->selectRow( 'reading_list_entry', '*',
                        [ 'rle_rl_id' => $listId, 'rle_deleted' => 1 ] );
-               $this->assertEquals( 'foo', $row->rle_project );
+               $this->assertEquals( $fooProjectId, $row->rle_rlp_id );
                $this->assertTimestampEquals( wfTimestampNow(), 
$row->rle_date_updated );
 
                $this->assertFailsWith( 
'readinglists-db-error-no-such-list-entry',
@@ -750,18 +758,18 @@
                                'rl_name' => 'one',
                                'entries' => [
                                        [
-                                               'rle_project' => 'new',
+                                               'rlp_project' => 'new',
                                                'rle_title' => 'new',
                                                'rle_date_updated' => 
'20150101000000',
                                        ],
                                        [
-                                               'rle_project' => 'deleted',
+                                               'rlp_project' => 'deleted',
                                                'rle_title' => 'deleted',
                                                'rle_deleted' => 1,
                                                'rle_date_updated' => 
'20150101000000',
                                        ],
                                        [
-                                               'rle_project' => 'old',
+                                               'rlp_project' => 'old',
                                                'rle_title' => 'old',
                                                'rle_date_updated' => 
'20080101000000',
                                        ],
@@ -771,7 +779,7 @@
                                'rl_name' => 'two',
                                'entries' => [
                                        [
-                                               'rle_project' => 'other',
+                                               'rlp_project' => 'other',
                                                'rle_title' => 'other',
                                                'rle_date_updated' => 
'20150101000000',
                                        ],
@@ -782,7 +790,7 @@
                                'rl_deleted' => 1,
                                'entries' => [
                                        [
-                                               'rle_project' => 'parent 
deleted',
+                                               'rlp_project' => 'parent 
deleted',
                                                'rle_title' => 'parent deleted',
                                                'rle_date_updated' => 
'20150101000000',
                                        ],
@@ -811,18 +819,18 @@
                $repository = new ReadingListRepository( null, $this->db, 
$this->db, $this->lbFactory );
                $entries = [
                        [
-                               'rle_project' => '-',
+                               'rlp_project' => '-',
                                'rle_title' => 'kept',
                                'rle_date_updated' => wfTimestampNow(),
                        ],
                        [
-                               'rle_project' => '-',
+                               'rlp_project' => '-',
                                'rle_title' => 'deleted-new',
                                'rle_date_updated' => '20150101000000',
                                'rle_deleted' => 1,
                        ],
                        [
-                               'rle_project' => '-',
+                               'rlp_project' => '-',
                                'rle_title' => 'deleted-old',
                                'rle_date_updated' => '20080101000000',
                                'rle_deleted' => 1,
@@ -871,7 +879,7 @@
                                'rl_name' => 'first',
                                'entries' => [
                                        [
-                                               'rle_project' => '-',
+                                               'rlp_project' => '-',
                                                'rle_title' => 'o',
                                        ],
                                ],
@@ -881,7 +889,7 @@
                                'rl_name' => 'second',
                                'entries' => [
                                        [
-                                               'rle_project' => '-',
+                                               'rlp_project' => '-',
                                                'rle_title' => 'x',
                                                'rle_deleted' => 1,
                                        ],
@@ -893,7 +901,7 @@
                                'rl_deleted' => 1,
                                'entries' => [
                                        [
-                                               'rle_project' => '-',
+                                               'rlp_project' => '-',
                                                'rle_title' => 'x',
                                        ],
                                ],
@@ -903,11 +911,11 @@
                                'rl_name' => 'fourth',
                                'entries' => [
                                        [
-                                               'rle_project' => '-',
+                                               'rlp_project' => '-',
                                                'rle_title' => 'x',
                                        ],
                                        [
-                                               'rle_project' => '-',
+                                               'rlp_project' => '-',
                                                'rle_title' => 'o',
                                        ],
                                ],
@@ -917,11 +925,11 @@
                                'rl_name' => 'fifth',
                                'entries' => [
                                        [
-                                               'rle_project' => '-',
+                                               'rlp_project' => '-',
                                                'rle_title' => 'x',
                                        ],
                                        [
-                                               'rle_project' => '-',
+                                               'rlp_project' => '-',
                                                'rle_title' => 'o',
                                        ],
                                ],
@@ -1033,6 +1041,7 @@
        /**
         * Creates reading_list_entry rows from the given data, with some magic 
fields:
         * - missing list ids will be filled automatically
+        * - 'rlp_project' will be handled appropriately
         * @param int $listId The list to add entries to
         * @param int $userId Th central ID of the list owner
         * @param array[] $entries Array of rows for reading_list_entry, with 
some magic fields
@@ -1047,11 +1056,43 @@
                        if ( !isset( $entry['rle_user_id'] ) ) {
                                $entry['rle_user_id'] = $userId;
                        }
+                       if ( isset( $entry['rlp_project'] ) ) {
+                               list( $projectId ) = $this->addProjects( [ 
$entry['rlp_project'] ] );
+                               unset( $entry['rlp_project'] );
+                               $entry['rle_rlp_id'] = $projectId;
+                       }
                        $this->db->insert( 'reading_list_entry', $entry );
                        $entryId = $this->db->insertId();
                        $entryIds[] = $entryId;
                }
                return $entryIds;
+       }
+
+       /**
+        * Creates reading_list_project rows from the given data.
+        * @param string[] $projects
+        * @return int[] Project IDs
+        */
+       private function addProjects( array $projects ) {
+               $ids = [];
+               foreach ( $projects as $project ) {
+                       $this->db->insert(
+                               'reading_list_project',
+                               [ 'rlp_project' => $project ],
+                               __METHOD__,
+                               [ 'IGNORE' ]
+                       );
+                       $projectId = $this->db->insertId();
+                       if ( !$projectId ) {
+                               $projectId = $this->db->selectField(
+                                       'reading_list_project',
+                                       'rlp_id',
+                                       [ 'rlp_project' => $project ]
+                               );
+                       }
+                       $ids[] = $projectId;
+               }
+               return $ids;
        }
 
        private function addDataForAnotherUser() {
@@ -1064,14 +1105,14 @@
                                'rl_deleted' => 0,
                                'entries' => [
                                        [
-                                               'rle_project' => 'foo',
+                                               'rlp_project' => 'foo',
                                                'rle_title' => 'bar',
                                                'rle_date_created' => 
wfTimestampNow(),
                                                'rle_date_updated' => 
wfTimestampNow(),
                                                'rle_deleted' => 0,
                                        ],
                                        [
-                                               'rle_project' => 'foo2',
+                                               'rlp_project' => 'foo2',
                                                'rle_title' => 'bar2',
                                                'rle_date_created' => 
wfTimestampNow(),
                                                'rle_date_updated' => 
wfTimestampNow(),

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I047d1493f1d9f51d733c1925a38c080829589f35
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/ReadingLists
Gerrit-Branch: master
Gerrit-Owner: Gergő Tisza <gti...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to