From 225c4c66cbc2b501f10d15a64b179cd7d3a00a57 Mon Sep 17 00:00:00 2001 From: Spine Date: Wed, 11 Jun 2025 08:50:36 +0000 Subject: [PATCH] add request_vote_summary table for ordering searches --- app/DB/Pg.php | 13 ++- app/Manager/Request.php | 84 ++++++++++++------- app/Request.php | 7 +- app/Request/AbstractValue.php | 4 + bin/ut-garbage-collect | 31 +++---- .../20250610000000_request_vote_summary.php | 36 ++++++++ tests/phpunit/DbTest.php | 2 +- tests/phpunit/RequestTest.php | 1 - 8 files changed, 126 insertions(+), 52 deletions(-) create mode 100644 misc/pg-migrations/20250610000000_request_vote_summary.php diff --git a/app/DB/Pg.php b/app/DB/Pg.php index 8c8becb75..12dbe500a 100644 --- a/app/DB/Pg.php +++ b/app/DB/Pg.php @@ -106,10 +106,15 @@ class Pg { public function prepared_query(string $query, ...$args): int { $begin = microtime(true); $st = $this->prepare($query); - if ($st->execute([...$args])) { - $rowCount = $st->rowCount(); - $this->stats->register($query, $rowCount, $begin, [...$args]); - return $rowCount; + try { + if ($st->execute([...$args])) { + $rowCount = $st->rowCount(); + $this->stats->register($query, $rowCount, $begin, [...$args]); + return $rowCount; + } + } catch (\PDOException $e) { + throw new \PDOException($e->getMessage() + . " $query " . json_encode($args, JSON_UNESCAPED_SLASHES)); } $this->stats->error($query); return 0; diff --git a/app/Manager/Request.php b/app/Manager/Request.php index be540dca0..d7246a949 100644 --- a/app/Manager/Request.php +++ b/app/Manager/Request.php @@ -27,6 +27,7 @@ class Request extends \Gazelle\BaseManager { Media $media, LogCue $logCue, string $oclc, + array $tagList = [], int|null $groupId = null, ): \Gazelle\Request { self::$db->prepared_query(' @@ -41,6 +42,30 @@ class Request extends \Gazelle\BaseManager { $logCue->dbValue(), $logCue->needLogChecksum ? 1 : 0, $oclc, $groupId ); $request = new \Gazelle\Request(self::$db->inserted_id()); + $this->pg()->prepared_query(" + insert into request ( + id_request, id_user, id_tgroup, id_category, id_release_type, year, + last_vote, created, modified, + description, title, image, catalogue_number, record_label, + log_score, need_log, need_cue, need_checksum, + artist_title_ts, encoding_str, format_str, media_str, tag + ) values ( + ?, ?, ?, ?, ?, ?, + ?, ?, ?, + ?, ?, ?, ?, ?, + ?, ?, ?, ?, + to_tsvector('simple', ?), + array[" . placeholders($encoding->dbList()) . "]::text[], + array[" . placeholders($format->dbList()) . "]::text[], + array[" . placeholders($media->dbList()) . "]::text[], + array[" . placeholders($tagList) . "]::int[] + ) + ", $request->id, $user->id, $groupId, $categoryId, $releaseType, $year, + $request->lastVoteDate(), $request->created(), $request->modified(), + $description, $title, $image, $catalogueNumber, $recordLabel, + $logCue->minScore, $logCue->needLog ? 't' : 'f', $logCue->needCue ? 't' : 'f', $logCue->needLogChecksum ? 't' : 'f', + $title, ...$encoding->dbList(), ...$format->dbList(), ...$media->dbList(), ...$tagList, + ); $request->vote($user, $bounty); $request->artistFlush(); return $request; @@ -177,8 +202,10 @@ class Request extends \Gazelle\BaseManager { coalesce(position('Log' in rr.\"LogCue\") > 0, false) as need_log, coalesce(position('Cue' in rr.\"LogCue\") > 0, false) as need_cue, case when rr.\"Checksum\" = 0 then false else true end as need_checksum, - rr.\"BitrateList\" as encoding, rr.\"FormatList\" as format, rr.\"MediaList\" as media, a_t.artist_title_ts, + string_to_array(rr.\"BitrateList\", '|') as encoding_str, + string_to_array(rr.\"FormatList\", '|') as format_str, + string_to_array(rr.\"MediaList\", '|') as media_str, coalesce(tl.tag, ARRAY[]::int[]) as tag from relay.requests rr inner join a_t on (a_t.id_request = rr.\"ID\") @@ -186,41 +213,38 @@ class Request extends \Gazelle\BaseManager { ) as i on r.id_request = i.id_request when not matched then insert ( - id_request, id_user, id_filler, id_torrent, - id_tgroup, id_category, id_release_type, year, - last_vote, filled, created, modified, - description, title, image, catalogue_number, - record_label, log_score, - need_log, need_cue, need_checksum, - encoding_str, format_str, media_str, - artist_title_ts, tag + id_request, id_user, id_tgroup, id_category, + id_release_type, year, + last_vote, created, modified, + description, title, image, catalogue_number, record_label, + log_score, need_log, need_cue, need_checksum, + artist_title_ts, tag, encoding_str, format_str, media_str ) values ( - i.id_request, i.id_user, i.id_filler, i.id_torrent, - i.id_tgroup, i.id_category, i.id_release_type, i.year, - i.last_vote, i.filled, i.created, i.updated, - i.description, i.title, i.image, i.catalogue_number, - i.record_label, i.log_score, - i.need_log, i.need_cue, i.need_checksum, - string_to_array(i.encoding::text, '|'), - string_to_array(i.format::text, '|'), - string_to_array(i.media::text, '|'), - i.artist_title_ts, i.tag + i.id_request, i.id_user, i.id_tgroup, i.id_category, + i.id_release_type, i.year, + i.last_vote, i.created, i.updated, + i.description, i.title, i.image, i.catalogue_number, i.record_label, + i.log_score, i.need_log, i.need_cue, i.need_checksum, + i.artist_title_ts, + i.tag, + string_to_array(i.encoding_str::text, '|'), + string_to_array(i.format_str::text, '|'), + string_to_array(i.media_str::text, '|') ) when matched then update set - id_user = i.id_user, id_filler = i.id_filler, id_torrent = i.id_torrent, - id_tgroup = i.id_tgroup, id_category = i.id_category, + filled = i.filled, id_filler = i.id_filler, id_torrent = i.id_torrent, + id_user = i.id_user, id_tgroup = i.id_tgroup, id_category = i.id_category, id_release_type = i.id_release_type, year = i.year, - last_vote = i.last_vote, filled = i.filled, created = i.created, - modified = i.updated, description = i.description, title = i.title, - image = i.image, catalogue_number = i.catalogue_number, - record_label = i.record_label, log_score = i.log_score, need_log = i.need_log, + last_vote = i.last_vote, created = i.created, modified = i.updated, + description = i.description, title = i.title, image = i.image, + catalogue_number = i.catalogue_number, record_label = i.record_label, + log_score = i.log_score, need_log = i.need_log, need_cue = i.need_cue, need_checksum = i.need_checksum, - encoding_str = string_to_array(i.encoding::text, '|'), - format_str = string_to_array(i.format::text, '|'), - media_str = string_to_array(i.media::text, '|'), - artist_title_ts = i.artist_title_ts, - tag = i.tag + artist_title_ts = i.artist_title_ts, tag = i.tag, + encoding_str = string_to_array(i.encoding_str::text, '|'), + format_str = string_to_array(i.format_str::text, '|'), + media_str = string_to_array(i.media_str::text, '|') "); } } diff --git a/app/Request.php b/app/Request.php index a599ac048..8a5bcc0b1 100644 --- a/app/Request.php +++ b/app/Request.php @@ -135,7 +135,8 @@ class Request extends BaseObject implements CategoryHasArtist { r.BitrateList AS encoding_list, r.FormatList AS format_list, r.MediaList AS media_list, - r.OCLC AS oclc + r.OCLC AS oclc, + r.updated AS modified FROM requests r INNER JOIN category c ON (c.category_id = r.CategoryID) LEFT JOIN release_type rel ON (rel.ID = r.ReleaseType) @@ -364,6 +365,10 @@ class Request extends BaseObject implements CategoryHasArtist { return $this->info()['media_list']; } + public function modified(): string { + return $this->info()['modified']; + } + public function logCue(): Request\LogCue { return $this->info()['logCue']; } diff --git a/app/Request/AbstractValue.php b/app/Request/AbstractValue.php index 1ec1ddb61..6370f4ef2 100644 --- a/app/Request/AbstractValue.php +++ b/app/Request/AbstractValue.php @@ -34,6 +34,10 @@ abstract class AbstractValue { return $this->all() || array_search($value, $this->label) !== false; } + public function dbList(): array { + return array_values($this->label); + } + public function dbValue(): string { return $this->all || count($this->label) == count($this->legal()) ? 'Any' diff --git a/bin/ut-garbage-collect b/bin/ut-garbage-collect index 3deb0a6f9..bdb67f4b6 100755 --- a/bin/ut-garbage-collect +++ b/bin/ut-garbage-collect @@ -81,13 +81,26 @@ $db->prepared_query(" WHERE um.ID IS NULL "); +$userMan = new Manager\User(); +$collageMan = new Manager\Collage(); +$collageMan->requestContext()->setViewer( + $userMan->findById( + (int)$db->scalar(' + SELECT um.id + FROM users_main um + INNER JOIN permissions p on (p.ID = um.PermissionID) + ORDER BY p.level DESC + LIMIT 1 + ') + ) +); + // clean collages $db->prepared_query(" SELECT ID FROM collages WHERE Name regexp '^(phpunit collage (?:ajax|artist|comment|contrib|personal|report) )' "); -$collageMan = new Manager\Collage(); foreach ($db->collect(0) as $collageId) { $collage = $collageMan->findById($collageId); echo "collage {$collage->id()} ({$collage->name()})\n"; @@ -148,23 +161,11 @@ foreach ($torrentList as $torrentId) { $torrent->removeTorrent(null, 'garbage collection', -1); } } -$userMan = new Manager\User(); -$tgMan = new Manager\TGroup(); -$tgMan->requestContext()->setViewer( - $userMan->findById( - (int)$db->scalar(' - SELECT um.id - FROM users_main um - INNER JOIN permissions p on (p.ID = um.PermissionID) - ORDER BY p.level DESC - LIMIT 1 - ') - ) -); +$tgMan = new Manager\TGroup(); foreach ($groupList as $tgroupId) { $tgroup = $tgMan->findById($tgroupId); - if ($tgroup) { + if ($tgroup instanceof TGroup) { echo "tgroup $torrentId ({$tgroup->name()})\n"; $tgroup->remove(new User(1)); } diff --git a/misc/pg-migrations/20250610000000_request_vote_summary.php b/misc/pg-migrations/20250610000000_request_vote_summary.php new file mode 100644 index 000000000..918d86c68 --- /dev/null +++ b/misc/pg-migrations/20250610000000_request_vote_summary.php @@ -0,0 +1,36 @@ +query(" + alter table request_vote add foreign key (id_request) + references request on delete cascade + "); + $this->query(" + create table request_vote_summary ( + id_request int not null primary key + references request on delete cascade, + bounty bigint not null + ) + "); + $this->query(" + insert into request_vote_summary (id_request, bounty) + select r.id_request, coalesce(sum(rv.bounty)::bigint, 0::bigint) + from request r + left join request_vote rv using (id_request) + group by r.id_request + "); + $this->query(" + create index rvs_b_idx on request_vote_summary (bounty) + "); + } + + public function down(): void { + $this->query("alter table request_vote drop constraint request_vote_id_request_fkey"); + $this->table("request_vote_summary")->drop()->save(); + } +} diff --git a/tests/phpunit/DbTest.php b/tests/phpunit/DbTest.php index 01e6c3b2f..19310c533 100644 --- a/tests/phpunit/DbTest.php +++ b/tests/phpunit/DbTest.php @@ -573,7 +573,7 @@ class DbTest extends TestCase { public function testPgWrite(): void { $this->expectException(\PDOException::class); $this->expectExceptionMessageMatches( - '/^SQLSTATE\[\d+\]: Insufficient privilege: \d+ ERROR: permission denied for table counter$/' + '/^SQLSTATE\[\d+\]: Insufficient privilege: \d+ ERROR: permission denied for table counter/' ); $this->pgro()->prepared_query(" insert into counter values ('phpunit-testWrite', 'fail on insert', 0) diff --git a/tests/phpunit/RequestTest.php b/tests/phpunit/RequestTest.php index a510acee8..ad08863a6 100644 --- a/tests/phpunit/RequestTest.php +++ b/tests/phpunit/RequestTest.php @@ -807,7 +807,6 @@ class RequestTest extends TestCase { $this->request = Helper::makeRequestMusic($user, $title); $artistName = 'artist ftsreq ' . randomString(); $this->request->artistRole()->set([ARTIST_MAIN => [$artistName]], $user); - new Manager\Request()->relay(); $this->assertEquals( $this->request->id,