From b70edd1dd3dc921b484c92b1959d89bac088abb2 Mon Sep 17 00:00:00 2001 From: dartcafe Date: Fri, 3 May 2024 21:31:51 +0200 Subject: [PATCH 1/7] draft Signed-off-by: dartcafe --- lib/Db/Poll.php | 4 ++++ lib/Db/PollMapper.php | 43 +++++++++++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php index fb9f56277..b87e5e2a2 100644 --- a/lib/Db/Poll.php +++ b/lib/Db/Poll.php @@ -138,6 +138,8 @@ class Poll extends EntityWithUser implements JsonSerializable { protected int $currentUserVotes = 0; protected string $userRole = "none"; protected ?int $isCurrentUserLocked = 0; + protected ?int $currentUserVotesSub = 0; + protected ?int $currentUserVotesYesSub = 0; public function __construct() { $this->addType('created', 'int'); @@ -195,6 +197,8 @@ public function jsonSerialize(): array { 'yesByCurrentUser' => $this->getCurrentUserYesVotes(), 'countVotes' => $this->getCurrentUserCountVotes(), 'userRole' => $this->getUserRole(), + 'yesVotesSub' => $this->getCurrentUserVotesYesSub(), + 'countVotesSub' => $this->getCurrentUserVotesSub(), ], ]; } diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php index 12726301f..9be13dc0e 100644 --- a/lib/Db/PollMapper.php +++ b/lib/Db/PollMapper.php @@ -174,9 +174,12 @@ protected function buildQuery(): IQueryBuilder { $qb = $this->db->getQueryBuilder(); $qb->select(self::TABLE . '.*') - // TODO: check if this is necessary, in case of empty table to avoid possibly nulled columns - // ->groupBy(self::TABLE . '.id') - ->from($this->getTableName(), self::TABLE); + ->from($this->getTableName(), self::TABLE) + ->groupBy(self::TABLE . '.id'); + + $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $currentUserId)->getSQL() . ')'), 'current_user_votes_sub'); + $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $currentUserId, Vote::VOTE_YES)->getSQL() . ')'), 'current_user_votes_yes_sub'); + $this->joinOptionsForMaxDate($qb, self::TABLE); $this->joinCurrentUserVotes($qb, self::TABLE, $currentUserId); $this->joinUserRole($qb, self::TABLE, $currentUserId); @@ -185,10 +188,7 @@ protected function buildQuery(): IQueryBuilder { } /** - * Joins options to evaluate min and max option date for date polls - * if text poll or no options are set, - * the min value is the current time, - * the max value is null + * Joins shares to evaluate user role */ protected function joinUserRole(IQueryBuilder &$qb, string $fromAlias, string $currentUserId): void { $joinAlias = 'shares'; @@ -227,14 +227,11 @@ protected function joinOptionsForMaxDate(IQueryBuilder &$qb, string $fromAlias): } /** - * Joins options to evaluate min and max option date for date polls - * if text poll or no options are set, - * the min value is the current time, - * the max value is null + * Joins votes to evaluate current user votes */ protected function joinCurrentUserVotes(IQueryBuilder &$qb, string $fromAlias, $currentUserId): void { $joinAlias = 'user_vote'; - // force value into a MIN function to avoid grouping errors + $qb->selectAlias($qb->func()->count($joinAlias . '.vote_answer'), 'current_user_votes'); $qb->leftJoin( @@ -248,4 +245,26 @@ protected function joinCurrentUserVotes(IQueryBuilder &$qb, string $fromAlias, $ ); } + /** + * Joins options to evaluate min and max option date for date polls + * if text poll or no options are set, + * the min value is the current time, + * the max value is null + */ + protected function subQueryVotesCount(string $fromAlias, string $currentUserId, string $answerFilter = ''): IQueryBuilder { + $subAlias = 'user_vote_sub'; + + $subQuery = $this->db->getQueryBuilder(); + $subQuery->select($subQuery->func()->count($subAlias . '.vote_answer')) + ->from(Vote::TABLE, $subAlias) + ->where($subQuery->expr()->eq($subAlias . '.poll_id', $fromAlias . '.id')) + ->andWhere($subQuery->expr()->eq($subAlias . '.user_id', $subQuery->createNamedParameter($currentUserId, IQueryBuilder::PARAM_STR))); + + if ($answerFilter === Vote::VOTE_YES) { + $subQuery->andWhere($subQuery->expr()->eq($subAlias . '.vote_answer', $subQuery->createNamedParameter($answerFilter, IQueryBuilder::PARAM_STR))); + } + return $subQuery; + + } + } From 70fc51295c009f24b89ad0054cfb8075cf9f9d9c Mon Sep 17 00:00:00 2001 From: dartcafe Date: Sat, 4 May 2024 00:14:08 +0200 Subject: [PATCH 2/7] create param outside of subquery method Signed-off-by: dartcafe --- lib/Db/PollMapper.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php index 9be13dc0e..cd286c4b9 100644 --- a/lib/Db/PollMapper.php +++ b/lib/Db/PollMapper.php @@ -27,6 +27,7 @@ namespace OCA\Polls\Db; use OCP\AppFramework\Db\QBMapper; +use OCP\DB\QueryBuilder\IParameter; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\Search\ISearchQuery; @@ -177,13 +178,17 @@ protected function buildQuery(): IQueryBuilder { ->from($this->getTableName(), self::TABLE) ->groupBy(self::TABLE . '.id'); - $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $currentUserId)->getSQL() . ')'), 'current_user_votes_sub'); - $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $currentUserId, Vote::VOTE_YES)->getSQL() . ')'), 'current_user_votes_yes_sub'); + $paramUser = $qb->createNamedParameter($currentUserId, IQueryBuilder::PARAM_STR); + $paramAnswerYes = $qb->createNamedParameter(Vote::VOTE_YES, IQueryBuilder::PARAM_STR); + + $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser)->getSQL() . ')'), 'current_user_votes_sub'); + $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser, $paramAnswerYes)->getSQL() . ')'), 'current_user_votes_yes_sub'); $this->joinOptionsForMaxDate($qb, self::TABLE); $this->joinCurrentUserVotes($qb, self::TABLE, $currentUserId); $this->joinUserRole($qb, self::TABLE, $currentUserId); $qb->groupBy(self::TABLE . '.id'); + return $qb; } @@ -251,17 +256,17 @@ protected function joinCurrentUserVotes(IQueryBuilder &$qb, string $fromAlias, $ * the min value is the current time, * the max value is null */ - protected function subQueryVotesCount(string $fromAlias, string $currentUserId, string $answerFilter = ''): IQueryBuilder { + protected function subQueryVotesCount(string $fromAlias, IParameter $currentUserId, ?IParameter $answerFilter = null): IQueryBuilder { $subAlias = 'user_vote_sub'; $subQuery = $this->db->getQueryBuilder(); $subQuery->select($subQuery->func()->count($subAlias . '.vote_answer')) ->from(Vote::TABLE, $subAlias) ->where($subQuery->expr()->eq($subAlias . '.poll_id', $fromAlias . '.id')) - ->andWhere($subQuery->expr()->eq($subAlias . '.user_id', $subQuery->createNamedParameter($currentUserId, IQueryBuilder::PARAM_STR))); + ->andWhere($subQuery->expr()->eq($subAlias . '.user_id', $currentUserId)); - if ($answerFilter === Vote::VOTE_YES) { - $subQuery->andWhere($subQuery->expr()->eq($subAlias . '.vote_answer', $subQuery->createNamedParameter($answerFilter, IQueryBuilder::PARAM_STR))); + if ($answerFilter) { + $subQuery->andWhere($subQuery->expr()->eq($subAlias . '.vote_answer', $answerFilter)); } return $subQuery; From 385ed5ce30e203a86107c68cf630289434b9e21b Mon Sep 17 00:00:00 2001 From: dartcafe Date: Sat, 4 May 2024 00:24:33 +0200 Subject: [PATCH 3/7] finish changes Signed-off-by: dartcafe --- lib/Db/Poll.php | 28 +++++++++------------------- lib/Db/PollMapper.php | 10 +++------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php index b87e5e2a2..80ddeab99 100644 --- a/lib/Db/Poll.php +++ b/lib/Db/Poll.php @@ -80,6 +80,8 @@ * @method void setMiscSettings(string $value) * @method int getMinDate() * @method int getMaxDate() + * @method int getCurrentUserVotes() + * @method int getCurrentUserVotesYes() */ class Poll extends EntityWithUser implements JsonSerializable { @@ -135,11 +137,12 @@ class Poll extends EntityWithUser implements JsonSerializable { protected bool $hasOrphanedVotes = false; protected int $maxDate = 0; protected int $minDate = 0; - protected int $currentUserVotes = 0; protected string $userRole = "none"; protected ?int $isCurrentUserLocked = 0; - protected ?int $currentUserVotesSub = 0; - protected ?int $currentUserVotesYesSub = 0; + + // subqueried columns + protected int $currentUserVotes = 0; + protected int $currentUserVotesYes = 0; public function __construct() { $this->addType('created', 'int'); @@ -193,12 +196,10 @@ public function jsonSerialize(): array { 'voteLimit' => $this->getVoteLimit(), 'lastInteraction' => $this->getLastInteraction(), 'summary' => [ - 'orphanedVotes' => $this->getCurrentUserOrphanedVotes(), - 'yesByCurrentUser' => $this->getCurrentUserYesVotes(), - 'countVotes' => $this->getCurrentUserCountVotes(), 'userRole' => $this->getUserRole(), - 'yesVotesSub' => $this->getCurrentUserVotesYesSub(), - 'countVotesSub' => $this->getCurrentUserVotesSub(), + 'orphanedVotes' => $this->getCurrentUserOrphanedVotes(), + 'yesVotes' => $this->getCurrentUserVotesYes(), + 'countVotes' => $this->getCurrentUserVotes(), ], ]; } @@ -346,10 +347,6 @@ public function getRelevantThresholdNet(): int { ); } - public function getCurrentUserCountVotes(): int { - return $this->currentUserVotes; - } - public function getIsCurrentUserLocked(): bool { return (bool) $this->isCurrentUserLocked; } @@ -361,13 +358,6 @@ public function getCurrentUserOrphanedVotes(): int { return count($this->voteMapper->findOrphanedByPollandUser($this->id, $this->userMapper->getCurrentUserCached()->getId())); } - /** - * @psalm-return int<0, max> - */ - public function getCurrentUserYesVotes(): int { - return count($this->voteMapper->getYesVotesByParticipant($this->getPollId(), $this->userMapper->getCurrentUserCached()->getId())); - } - public function getDeadline(): int { // if expiration is set return expiration date if ($this->getExpire()) { diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php index cd286c4b9..1e2aac427 100644 --- a/lib/Db/PollMapper.php +++ b/lib/Db/PollMapper.php @@ -181,11 +181,10 @@ protected function buildQuery(): IQueryBuilder { $paramUser = $qb->createNamedParameter($currentUserId, IQueryBuilder::PARAM_STR); $paramAnswerYes = $qb->createNamedParameter(Vote::VOTE_YES, IQueryBuilder::PARAM_STR); - $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser)->getSQL() . ')'), 'current_user_votes_sub'); - $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser, $paramAnswerYes)->getSQL() . ')'), 'current_user_votes_yes_sub'); + $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser)->getSQL() . ')'), 'current_user_votes'); + $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser, $paramAnswerYes)->getSQL() . ')'), 'current_user_votes_yes'); $this->joinOptionsForMaxDate($qb, self::TABLE); - $this->joinCurrentUserVotes($qb, self::TABLE, $currentUserId); $this->joinUserRole($qb, self::TABLE, $currentUserId); $qb->groupBy(self::TABLE . '.id'); @@ -251,10 +250,7 @@ protected function joinCurrentUserVotes(IQueryBuilder &$qb, string $fromAlias, $ } /** - * Joins options to evaluate min and max option date for date polls - * if text poll or no options are set, - * the min value is the current time, - * the max value is null + * Subquery for votes count */ protected function subQueryVotesCount(string $fromAlias, IParameter $currentUserId, ?IParameter $answerFilter = null): IQueryBuilder { $subAlias = 'user_vote_sub'; From fd7c567cf2e153cef105c851b00fb7fb88d9d0f3 Mon Sep 17 00:00:00 2001 From: dartcafe Date: Sat, 4 May 2024 00:31:43 +0200 Subject: [PATCH 4/7] fixes and tidy Signed-off-by: dartcafe --- lib/Db/Poll.php | 12 ++++++------ lib/Db/PollMapper.php | 23 ++--------------------- lib/Db/VoteMapper.php | 26 -------------------------- 3 files changed, 8 insertions(+), 53 deletions(-) diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php index 80ddeab99..fddb5d998 100644 --- a/lib/Db/Poll.php +++ b/lib/Db/Poll.php @@ -80,8 +80,8 @@ * @method void setMiscSettings(string $value) * @method int getMinDate() * @method int getMaxDate() - * @method int getCurrentUserVotes() - * @method int getCurrentUserVotesYes() + * @method int getCurrentUserCountVotes() + * @method int getCurrentUserCountVotesYes() */ class Poll extends EntityWithUser implements JsonSerializable { @@ -141,8 +141,8 @@ class Poll extends EntityWithUser implements JsonSerializable { protected ?int $isCurrentUserLocked = 0; // subqueried columns - protected int $currentUserVotes = 0; - protected int $currentUserVotesYes = 0; + protected int $currentUserCountVotes = 0; + protected int $currentUserCountVotesYes = 0; public function __construct() { $this->addType('created', 'int'); @@ -198,8 +198,8 @@ public function jsonSerialize(): array { 'summary' => [ 'userRole' => $this->getUserRole(), 'orphanedVotes' => $this->getCurrentUserOrphanedVotes(), - 'yesVotes' => $this->getCurrentUserVotesYes(), - 'countVotes' => $this->getCurrentUserVotes(), + 'yesVotes' => $this->getCurrentUserCountVotesYes(), + 'countVotes' => $this->getCurrentUserCountVotes(), ], ]; } diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php index 1e2aac427..baae9cdcc 100644 --- a/lib/Db/PollMapper.php +++ b/lib/Db/PollMapper.php @@ -181,8 +181,8 @@ protected function buildQuery(): IQueryBuilder { $paramUser = $qb->createNamedParameter($currentUserId, IQueryBuilder::PARAM_STR); $paramAnswerYes = $qb->createNamedParameter(Vote::VOTE_YES, IQueryBuilder::PARAM_STR); - $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser)->getSQL() . ')'), 'current_user_votes'); - $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser, $paramAnswerYes)->getSQL() . ')'), 'current_user_votes_yes'); + $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser)->getSQL() . ')'), 'current_user_count_votes'); + $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser, $paramAnswerYes)->getSQL() . ')'), 'current_user_count_votes_yes'); $this->joinOptionsForMaxDate($qb, self::TABLE); $this->joinUserRole($qb, self::TABLE, $currentUserId); @@ -230,25 +230,6 @@ protected function joinOptionsForMaxDate(IQueryBuilder &$qb, string $fromAlias): ); } - /** - * Joins votes to evaluate current user votes - */ - protected function joinCurrentUserVotes(IQueryBuilder &$qb, string $fromAlias, $currentUserId): void { - $joinAlias = 'user_vote'; - - $qb->selectAlias($qb->func()->count($joinAlias . '.vote_answer'), 'current_user_votes'); - - $qb->leftJoin( - $fromAlias, - Vote::TABLE, - $joinAlias, - $qb->expr()->andX( - $qb->expr()->eq($joinAlias . '.poll_id', $fromAlias . '.id'), - $qb->expr()->eq($joinAlias . '.user_id', $qb->createNamedParameter($currentUserId, IQueryBuilder::PARAM_STR)), - ) - ); - } - /** * Subquery for votes count */ diff --git a/lib/Db/VoteMapper.php b/lib/Db/VoteMapper.php index 85119df66..13e65391a 100644 --- a/lib/Db/VoteMapper.php +++ b/lib/Db/VoteMapper.php @@ -134,32 +134,6 @@ public function deleteByPollAndUserId(int $pollId, string $userId): void { $qb->executeStatement(); } - /** - * @throws \OCP\AppFramework\Db\DoesNotExistException if not found - * @return Vote[] - * @psalm-return array - */ - public function getYesVotesByParticipant(int $pollId, string $userId): array { - $qb = $this->buildQuery(); - $qb->andWhere($qb->expr()->eq(self::TABLE . '.poll_id', $qb->createNamedParameter($pollId, IQueryBuilder::PARAM_INT))) - ->andWhere($qb->expr()->eq(self::TABLE . '.user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))) - ->andWhere($qb->expr()->eq(self::TABLE . '.vote_answer', $qb->createNamedParameter(Vote::VOTE_YES, IQueryBuilder::PARAM_STR))); - return $this->findEntities($qb); - } - - /** - * @throws \OCP\AppFramework\Db\DoesNotExistException if not found - * @return Vote[] - * @psalm-return array - */ - public function getYesVotesByOption(int $pollId, string $pollOptionText): array { - $qb = $this->buildQuery(); - $qb->andWhere($qb->expr()->eq(self::TABLE . '.poll_id', $qb->createNamedParameter($pollId, IQueryBuilder::PARAM_INT))) - ->andWhere($qb->expr()->eq(self::TABLE . '.vote_option_text', $qb->createNamedParameter($pollOptionText, IQueryBuilder::PARAM_STR))) - ->andWhere($qb->expr()->eq(self::TABLE . '.vote_answer', $qb->createNamedParameter(Vote::VOTE_YES, IQueryBuilder::PARAM_STR))); - return $this->findEntities($qb); - } - public function renameUserId(string $userId, string $replacementName): void { $query = $this->db->getQueryBuilder(); $query->update($this->getTableName()) From 8e0a8bc4d83b744b3ae031ad37f6d7a304566c94 Mon Sep 17 00:00:00 2001 From: dartcafe Date: Sat, 4 May 2024 11:34:43 +0200 Subject: [PATCH 5/7] count orphaned votes via subquery Signed-off-by: dartcafe --- lib/Db/Poll.php | 32 +++++++++++++++++++------------- lib/Db/PollMapper.php | 33 +++++++++++++++++++++++++++++++++ lib/Db/VoteMapper.php | 2 +- 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php index fddb5d998..83ff63a70 100644 --- a/lib/Db/Poll.php +++ b/lib/Db/Poll.php @@ -78,8 +78,15 @@ * @method void setLastInteraction(int $value) * @method string getMiscSettings() * @method void setMiscSettings(string $value) + * + * Magic functions for joined columns + * @method ?int getIsCurrentUserLocked() * @method int getMinDate() * @method int getMaxDate() + * @method int getUserRole() + * + * Magic functions for subqueried columns + * @method int getCurrentUserCountOrphanedVotes() * @method int getCurrentUserCountVotes() * @method int getCurrentUserCountVotesYes() */ @@ -107,7 +114,6 @@ class Poll extends EntityWithUser implements JsonSerializable { private IURLGenerator $urlGenerator; protected UserMapper $userMapper; - private VoteMapper $voteMapper; // schema columns public $id = null; @@ -134,13 +140,13 @@ class Poll extends EntityWithUser implements JsonSerializable { protected ?string $miscSettings = ''; // joined columns - protected bool $hasOrphanedVotes = false; + protected ?int $isCurrentUserLocked = 0; protected int $maxDate = 0; protected int $minDate = 0; protected string $userRole = "none"; - protected ?int $isCurrentUserLocked = 0; // subqueried columns + protected int $currentUserCountOrphanedVotes = 0; protected int $currentUserCountVotes = 0; protected int $currentUserCountVotesYes = 0; @@ -158,12 +164,19 @@ public function __construct() { $this->addType('hideBookedUp', 'int'); $this->addType('useNo', 'int'); $this->addType('lastInteraction', 'int'); + + // joined columns + $this->addType('isCurrentUserLocked', 'int'); $this->addType('maxDate', 'int'); $this->addType('minDate', 'int'); - $this->addType('currentUserVotes', 'int'); + + // subqueried columns + $this->addType('currentUserCountVotes', 'int'); + $this->addType('currentUserCountVotesYes', 'int'); + $this->addType('currentUserCountOrphanedVotes', 'int'); + $this->urlGenerator = Container::queryClass(IURLGenerator::class); $this->userMapper = Container::queryClass(UserMapper::class); - $this->voteMapper = Container::queryClass(VoteMapper::class); } /** @@ -197,7 +210,7 @@ public function jsonSerialize(): array { 'lastInteraction' => $this->getLastInteraction(), 'summary' => [ 'userRole' => $this->getUserRole(), - 'orphanedVotes' => $this->getCurrentUserOrphanedVotes(), + 'orphanedVotes' => $this->getCurrentUserCountOrphanedVotes(), 'yesVotes' => $this->getCurrentUserCountVotesYes(), 'countVotes' => $this->getCurrentUserCountVotes(), ], @@ -351,13 +364,6 @@ public function getIsCurrentUserLocked(): bool { return (bool) $this->isCurrentUserLocked; } - /** - * @psalm-return int<0, max> - */ - public function getCurrentUserOrphanedVotes(): int { - return count($this->voteMapper->findOrphanedByPollandUser($this->id, $this->userMapper->getCurrentUserCached()->getId())); - } - public function getDeadline(): int { // if expiration is set return expiration date if ($this->getExpire()) { diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php index baae9cdcc..3ba9e8eed 100644 --- a/lib/Db/PollMapper.php +++ b/lib/Db/PollMapper.php @@ -183,6 +183,7 @@ protected function buildQuery(): IQueryBuilder { $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser)->getSQL() . ')'), 'current_user_count_votes'); $qb->selectAlias($qb->createFunction('(' . $this->subQueryVotesCount(self::TABLE, $paramUser, $paramAnswerYes)->getSQL() . ')'), 'current_user_count_votes_yes'); + $qb->selectAlias($qb->createFunction('(' . $this->subQueryOrphanedVotesCount(self::TABLE, $paramUser)->getSQL() . ')'), 'current_user_count_orphaned_votes'); $this->joinOptionsForMaxDate($qb, self::TABLE); $this->joinUserRole($qb, self::TABLE, $currentUserId); @@ -205,6 +206,7 @@ protected function joinUserRole(IQueryBuilder &$qb, string $fromAlias, string $c $qb->expr()->andX( $qb->expr()->eq($fromAlias . '.id', $joinAlias . '.poll_id'), $qb->expr()->eq($joinAlias . '.user_id', $qb->createNamedParameter($currentUserId, IQueryBuilder::PARAM_STR)), + $qb->expr()->eq($joinAlias . '.deleted', $qb->expr()->literal(0, IQueryBuilder::PARAM_INT)), ) ); } @@ -230,6 +232,8 @@ protected function joinOptionsForMaxDate(IQueryBuilder &$qb, string $fromAlias): ); } + + /** * Subquery for votes count */ @@ -242,11 +246,40 @@ protected function subQueryVotesCount(string $fromAlias, IParameter $currentUser ->where($subQuery->expr()->eq($subAlias . '.poll_id', $fromAlias . '.id')) ->andWhere($subQuery->expr()->eq($subAlias . '.user_id', $currentUserId)); + // filter by answer if ($answerFilter) { $subQuery->andWhere($subQuery->expr()->eq($subAlias . '.vote_answer', $answerFilter)); } + return $subQuery; + } + + /** + * Subquery for count of orphaned votes + */ + protected function subQueryOrphanedVotesCount(string $fromAlias, IParameter $currentUserId): IQueryBuilder { + $subAlias = 'user_vote_sub'; + $subJoinAlias = 'vote_options_join'; + // use subQueryVotesCount as base query + $subQuery = $this->subQueryVotesCount($fromAlias, $currentUserId); + + // superseed select, group result by voteId and add an additional condition + $subQuery->select($subQuery->func()->count($subAlias . '.vote_answer')) + ->andWhere($subQuery->expr()->isNull($subJoinAlias . '.id')); + + // join options to restrict query to votes with actually undeleted options + $subQuery->leftJoin( + $subAlias, + Option::TABLE, + $subJoinAlias, + $subQuery->expr()->andX( + $subQuery->expr()->eq($subJoinAlias . '.poll_id', $subAlias . '.poll_id'), + $subQuery->expr()->eq($subJoinAlias . '.poll_option_text', $subAlias . '.vote_option_text'), + $subQuery->expr()->eq($subJoinAlias . '.deleted', $subQuery->expr()->literal(0, IQueryBuilder::PARAM_INT)), + ) + ); + return $subQuery; } } diff --git a/lib/Db/VoteMapper.php b/lib/Db/VoteMapper.php index 13e65391a..442b0685a 100644 --- a/lib/Db/VoteMapper.php +++ b/lib/Db/VoteMapper.php @@ -227,7 +227,7 @@ protected function joinOption(IQueryBuilder &$qb, string $fromAlias): string { $qb->expr()->andX( $qb->expr()->eq($joinAlias . '.poll_id', $fromAlias . '.poll_id'), $qb->expr()->eq($joinAlias . '.poll_option_text', $fromAlias . '.vote_option_text'), - $qb->expr()->eq($joinAlias . '.deleted', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)), + $qb->expr()->eq($joinAlias . '.deleted', $qb->expr()->literal(0, IQueryBuilder::PARAM_INT)), ) ); From a300663c29039e0dce3b33c350131d6d44c68370 Mon Sep 17 00:00:00 2001 From: dartcafe Date: Sat, 4 May 2024 12:05:16 +0200 Subject: [PATCH 6/7] beautification and fix Signed-off-by: dartcafe --- lib/Db/Poll.php | 8 +++----- lib/Db/PollMapper.php | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php index 83ff63a70..337fb2e10 100644 --- a/lib/Db/Poll.php +++ b/lib/Db/Poll.php @@ -78,13 +78,11 @@ * @method void setLastInteraction(int $value) * @method string getMiscSettings() * @method void setMiscSettings(string $value) - * + * * Magic functions for joined columns - * @method ?int getIsCurrentUserLocked() * @method int getMinDate() * @method int getMaxDate() - * @method int getUserRole() - * + * * Magic functions for subqueried columns * @method int getCurrentUserCountOrphanedVotes() * @method int getCurrentUserCountVotes() @@ -361,7 +359,7 @@ public function getRelevantThresholdNet(): int { } public function getIsCurrentUserLocked(): bool { - return (bool) $this->isCurrentUserLocked; + return boolval($this->isCurrentUserLocked); } public function getDeadline(): int { diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php index 3ba9e8eed..938f0130b 100644 --- a/lib/Db/PollMapper.php +++ b/lib/Db/PollMapper.php @@ -264,7 +264,7 @@ protected function subQueryOrphanedVotesCount(string $fromAlias, IParameter $cur // use subQueryVotesCount as base query $subQuery = $this->subQueryVotesCount($fromAlias, $currentUserId); - // superseed select, group result by voteId and add an additional condition + // superseed select, group result by voteId and add an additional condition $subQuery->select($subQuery->func()->count($subAlias . '.vote_answer')) ->andWhere($subQuery->expr()->isNull($subJoinAlias . '.id')); From 81c0fa23ffdc8526592317c4db9a149a525c1b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 13 May 2024 14:11:32 +0200 Subject: [PATCH 7/7] fix: Fix quirks introduced by backport conflicts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/Db/Poll.php | 2 +- lib/Db/PollMapper.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php index 337fb2e10..7cb01e5fa 100644 --- a/lib/Db/Poll.php +++ b/lib/Db/Poll.php @@ -209,7 +209,7 @@ public function jsonSerialize(): array { 'summary' => [ 'userRole' => $this->getUserRole(), 'orphanedVotes' => $this->getCurrentUserCountOrphanedVotes(), - 'yesVotes' => $this->getCurrentUserCountVotesYes(), + 'yesByCurrentUser' => $this->getCurrentUserCountVotesYes(), 'countVotes' => $this->getCurrentUserCountVotes(), ], ]; diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php index 938f0130b..7ca975135 100644 --- a/lib/Db/PollMapper.php +++ b/lib/Db/PollMapper.php @@ -187,7 +187,6 @@ protected function buildQuery(): IQueryBuilder { $this->joinOptionsForMaxDate($qb, self::TABLE); $this->joinUserRole($qb, self::TABLE, $currentUserId); - $qb->groupBy(self::TABLE . '.id'); return $qb; }