Skip to content

Commit a073c67

Browse files
authored
Change indexing strategy for votes and options (#2777)
* check mysql instead of mariaDB * change index strategy * 4.1.4
1 parent dd0e450 commit a073c67

File tree

13 files changed

+179
-17
lines changed

13 files changed

+179
-17
lines changed

.github/workflows/phpunit.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666

6767
services:
6868
mysql:
69-
image: mariadb:10.5
69+
image: mysql:8.0
7070
ports:
7171
- 4444:3306/tcp
7272
env:

CHANGELOG.md

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# Changelog
22
All notable changes to this project will be documented in this file.
3-
## [4.1.3] - 2023-02-23
3+
## [4.1.4] - 2023-02-23
44
### Fix
5-
- Fix infinite updates call, if no polling type for watches were set (avoid server spamming)
6-
- Fix migrations and repair steps
5+
- Fix infinite updates call, if no polling type for watches were set (avoid server spamming) (v4.1.3)
6+
- Fix migrations and repair steps (v4.1.3)
7+
- Fix MySQL error 1071 Specified key was too long;
78
### changes
8-
- Change default of life update mechanism to manual updates instead of long polling
9-
9+
- Change default of life update mechanism to manual updates instead of long polling (v4.1.3)
10+
- Added Nextcloud 26
11+
1012
## [4.1.2] - 2023-01-23
1113
### Fix
1214
- Invitations are not send out if poll has no description (fix 2)

appinfo/info.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<name>Polls</name>
55
<summary>A polls app, similar to Doodle/Dudle with the possibility to restrict access.</summary>
66
<description>A polls app, similar to Doodle/Dudle with the possibility to restrict access (members, certain groups/users, hidden and public).</description>
7-
<version>4.1.3</version>
7+
<version>4.1.4</version>
88
<licence>agpl</licence>
99
<author>Vinzenz Rosenkranz</author>
1010
<author>René Gieling</author>
@@ -57,6 +57,7 @@
5757
<step>OCA\Polls\Migration\RepairSteps\RemoveIndices</step>
5858
<step>OCA\Polls\Migration\RepairSteps\CreateTables</step>
5959
<step>OCA\Polls\Migration\RepairSteps\DeleteInvalidRecords</step>
60+
<step>OCA\Polls\Migration\RepairSteps\UpdateHashes</step>
6061
</pre-migration>
6162
<post-migration>
6263
<step>OCA\Polls\Migration\RepairSteps\DropOrphanedTables</step>

lib/Command/Db/Rebuild.php

+16-1
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,12 @@ protected function runCommands(): int {
7272
$this->createOrUpdateSchema();
7373
$this->tableManager->migrate();
7474

75+
// validate and fix/create current table layout
76+
$this->printComment('Step 4. set hashes for votes and options');
77+
$this->migrateOptionsToHash();
78+
7579
// recreate indices and constraints
76-
$this->printComment('Step 4. Recreate indices and foreign key constraints');
80+
$this->printComment('Step 5. Recreate indices and foreign key constraints');
7781
// secure, that the schema is updated to the current status
7882
$this->indexManager->refreshSchema();
7983
$this->addForeignKeyConstraints();
@@ -132,6 +136,17 @@ private function createOrUpdateSchema(): void {
132136
}
133137
}
134138

139+
/**
140+
* Add or update hash for votes and options
141+
*/
142+
private function migrateOptionsToHash(): void {
143+
$this->printComment('- add or update hashes');
144+
$messages = $this->tableManager->migrateOptionsToHash();
145+
foreach ($messages as $message) {
146+
$this->printInfo(' - ' . $message);
147+
}
148+
}
149+
135150
private function removeObsoleteColumns(): void {
136151
$this->printComment('- Drop orphaned columns');
137152
$messages = $this->tableManager->removeObsoleteColumns();

lib/Db/Option.php

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
* @method void setPollId(integer $value)
4848
* @method string getPollOptionText()
4949
* @method void setPollOptionText(string $value)
50+
* @method string getPollOptionHash()
51+
* @method void setPollOptionHash(string $value)
5052
* @method int getReleased()
5153
* @method void setReleased(int $value)
5254
* @method int getTimestamp()
@@ -67,6 +69,9 @@ class Option extends EntityWithUser implements JsonSerializable {
6769
/** @var string $pollOptionText */
6870
protected $pollOptionText = '';
6971

72+
/** @var string $pollOptionHash */
73+
protected $pollOptionHash = '';
74+
7075
/** @var int $timestamp */
7176
protected $timestamp = 0;
7277

lib/Db/OptionMapper.php

+23
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
namespace OCA\Polls\Db;
2626

27+
use OCP\AppFramework\Db\Entity;
2728
use OCP\AppFramework\Db\QBMapper;
2829
use OCP\DB\Exception;
2930
use OCP\DB\QueryBuilder\IQueryBuilder;
@@ -40,6 +41,28 @@ public function __construct(IDBConnection $db) {
4041
parent::__construct($db, self::TABLE, Option::class);
4142
}
4243

44+
public function update(Entity $entity): Entity {
45+
$entity->setPollOptionHash(hash('md5', $entity->getPollId() . $entity->getPollOptionText() . $entity->getTimestamp()));
46+
return parent::update($entity);
47+
}
48+
49+
public function insert(Entity $entity): Entity {
50+
$entity->setPollOptionHash(hash('md5', $entity->getPollId() . $entity->getPollOptionText() . $entity->getTimestamp()));
51+
return parent::insert($entity);
52+
}
53+
54+
/**
55+
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
56+
* @return Option[]
57+
* @psalm-return array<array-key, Option>
58+
*/
59+
public function getAll(): array {
60+
$qb = $this->db->getQueryBuilder();
61+
62+
$qb->select('*')->from($this->getTableName());
63+
return $this->findEntities($qb);
64+
}
65+
4366
/**
4467
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
4568
*/

lib/Db/TableManager.php

+37-4
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ public function removeOrphaned(): void {
252252
public function deleteDuplicates(): array {
253253
$messages = [];
254254

255-
if ($this->schema->hasTable(Poll::TABLE)) {
255+
if ($this->schema->hasTable($this->dbPrefix . Poll::TABLE)) {
256256
$this->removeOrphaned();
257257

258258
$count[LogMapper::TABLE] = $this->logMapper->removeDuplicates();
@@ -290,9 +290,9 @@ public function removeObsoleteMigrations(): array {
290290
}
291291
return $messages;
292292
}
293-
public function fixVotes() {
294-
if ($this->schema->hasTable(OptionMapper::TABLE)) {
295-
$table = $this->schema->getTable(OptionMapper::TABLE);
293+
public function fixVotes(): void {
294+
if ($this->schema->hasTable($this->dbPrefix . OptionMapper::TABLE)) {
295+
$table = $this->schema->getTable($this->dbPrefix . OptionMapper::TABLE);
296296
if ($table->hasColumn('duration')) {
297297
$foundOptions = $this->optionMapper->findOptionsWithDuration();
298298
foreach ($foundOptions as $option) {
@@ -306,4 +306,37 @@ public function fixVotes() {
306306
}
307307
}
308308
}
309+
310+
public function migrateOptionsToHash(): array {
311+
$messages = [];
312+
313+
if ($this->schema->hasTable($this->dbPrefix . OptionMapper::TABLE)) {
314+
$table = $this->schema->getTable($this->dbPrefix . OptionMapper::TABLE);
315+
$count = 0;
316+
if ($table->hasColumn('poll_option_hash')) {
317+
foreach ($this->optionMapper->getAll() as $option) {
318+
$option->setPollOptionHash(hash('md5', $option->getPollId() . $option->getPollOptionText() . $option->getTimestamp()));
319+
320+
$this->optionMapper->update($option);
321+
$count++;
322+
}
323+
}
324+
$messages[] = 'Updated ' . $count . ' option hashes';
325+
}
326+
327+
328+
if ($this->schema->hasTable($this->dbPrefix . VoteMapper::TABLE)) {
329+
$table = $this->schema->getTable($this->dbPrefix . VoteMapper::TABLE);
330+
$count = 0;
331+
if ($table->hasColumn('vote_option_hash')) {
332+
foreach ($this->voteMapper->getAll() as $vote) {
333+
$vote->setVoteOptionHash(hash('md5', $vote->getPollId() . $vote->getUserId() . $vote->getVoteOptionText()));
334+
$this->voteMapper->update($vote);
335+
$count++;
336+
}
337+
}
338+
$messages[] = 'Updated ' . $count . ' vote hashes';
339+
}
340+
return $messages;
341+
}
309342
}

lib/Db/Vote.php

+5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
* @method void setVoteOptionId(integer $value)
3737
* @method string getVoteOptionText()
3838
* @method void setVoteOptionText(string $value)
39+
* @method string getVoteOptionHash()
40+
* @method void setVoteOptionHash(string $value)
3941
* @method string getVoteAnswer()
4042
* @method void setVoteAnswer(string $value)
4143
*/
@@ -54,6 +56,9 @@ class Vote extends EntityWithUser implements JsonSerializable {
5456
/** @var string $voteOptionText */
5557
protected $voteOptionText = '';
5658

59+
/** @var string $voteOptionHash */
60+
protected $voteOptionHash = '';
61+
5762
/** @var string $voteAnswer */
5863
protected $voteAnswer = '';
5964

lib/Db/VoteMapper.php

+24
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
namespace OCA\Polls\Db;
2626

27+
use OCP\AppFramework\Db\Entity;
2728
use OCP\AppFramework\Db\QBMapper;
2829
use OCP\DB\Exception;
2930
use OCP\DB\QueryBuilder\IQueryBuilder;
@@ -40,6 +41,29 @@ public function __construct(IDBConnection $db) {
4041
parent::__construct($db, self::TABLE, Vote::class);
4142
}
4243

44+
public function update(Entity $entity): Entity {
45+
$entity->setVoteOptionHash(hash('md5', $entity->getPollId() . $entity->getUserId() . $entity->getVoteOptionText()));
46+
return parent::update($entity);
47+
}
48+
49+
public function insert(Entity $entity): Entity {
50+
$entity->setVoteOptionHash(hash('md5', $entity->getPollId() . $entity->getUserId() . $entity->getVoteOptionText()));
51+
return parent::insert($entity);
52+
}
53+
54+
/**
55+
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
56+
* @return Vote[]
57+
* @psalm-return array<array-key, Vote>
58+
*/
59+
public function getAll(): array {
60+
$qb = $this->db->getQueryBuilder();
61+
62+
$qb->select('*')->from($this->getTableName());
63+
return $this->findEntities($qb);
64+
}
65+
66+
4367
/**
4468
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
4569
* @return Vote[]
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
* @copyright Copyright (c) 2021 René Gieling <[email protected]>
4+
*
5+
* @author René Gieling <[email protected]>
6+
*
7+
* @license GNU AGPL version 3 or any later version
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License as
11+
* published by the Free Software Foundation, either version 3 of the
12+
* License, or (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Affero General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Affero General Public License
20+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
*
22+
*/
23+
24+
25+
namespace OCA\Polls\Migration\RepairSteps;
26+
27+
use OCA\Polls\Db\TableManager;
28+
use OCP\Migration\IRepairStep;
29+
use OCP\Migration\IOutput;
30+
31+
class UpdateHashes implements IRepairStep {
32+
/** @var TableManager */
33+
private $tableManager;
34+
35+
public function __construct(
36+
TableManager $tableManager
37+
) {
38+
$this->tableManager = $tableManager;
39+
}
40+
41+
public function getName() {
42+
return 'Polls - Create hashes vor votes and options';
43+
}
44+
45+
public function run(IOutput $output): void {
46+
// Add hashes to votes and options
47+
$messages = $this->tableManager->migrateOptionsToHash();
48+
foreach ($messages as $message) {
49+
$output->info($message);
50+
}
51+
}
52+
}

lib/Migration/TableSchema.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ abstract class TableSchema {
5555
];
5656

5757
public const UNIQUE_INDICES = [
58-
Option::TABLE => ['name' => 'UNIQ_options', 'unique' => true, 'columns' => ['poll_id', 'poll_option_text', 'timestamp']],
58+
Option::TABLE => ['name' => 'UNIQ_options', 'unique' => true, 'columns' => ['poll_id', 'poll_option_hash', 'timestamp']],
5959
Log::TABLE => ['name' => 'UNIQ_unprocessed', 'unique' => true, 'columns' => ['processed', 'poll_id', 'user_id', 'message_id']],
6060
Subscription::TABLE => ['name' => 'UNIQ_subscription', 'unique' => true, 'columns' => ['poll_id', 'user_id']],
6161
Share::TABLE => ['name' => 'UNIQ_shares', 'unique' => true, 'columns' => ['poll_id', 'user_id']],
62-
Vote::TABLE => ['name' => 'UNIQ_votes', 'unique' => true, 'columns' => ['poll_id', 'user_id', 'vote_option_text']],
62+
Vote::TABLE => ['name' => 'UNIQ_votes', 'unique' => true, 'columns' => ['poll_id', 'user_id', 'vote_option_hash']],
6363
Preferences::TABLE => ['name' => 'UNIQ_preferences', 'unique' => true, 'columns' => ['user_id']],
6464
Watch::TABLE => ['name' => 'UNIQ_watch', 'unique' => true, 'columns' => ['poll_id', 'table', 'session_id']],
6565
];
@@ -172,6 +172,7 @@ abstract class TableSchema {
172172
'id' => ['type' => Types::BIGINT, 'options' => ['autoincrement' => true, 'notnull' => true, 'length' => 20]],
173173
'poll_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]],
174174
'poll_option_text' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 1024]],
175+
'poll_option_hash' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 256]],
175176
'timestamp' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]],
176177
'duration' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]],
177178
'order' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]],
@@ -185,6 +186,7 @@ abstract class TableSchema {
185186
'user_id' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 256]],
186187
'vote_option_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]],
187188
'vote_option_text' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 1024]],
189+
'vote_option_hash' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 256]],
188190
'vote_answer' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 64]],
189191
],
190192
Comment::TABLE => [

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "polls",
33
"description": "Polls app for nextcloud",
4-
"version": "4.1.3",
4+
"version": "4.1.4",
55
"authors": [
66
{
77
"name": "Vinzenz Rosenkranz",

0 commit comments

Comments
 (0)