Skip to content

Commit a54618b

Browse files
committed
Add isPublic field for query performance
Follow-up for ecamp#8004 Querying by isPrototype OR isShared is slow, because databases don't like OR conditions too much. We work around this by adding a combined isPublic field which is enforced to always contain isShared || isPrototype and have the DB queries filter by that.
1 parent 164176f commit a54618b

File tree

5 files changed

+72
-0
lines changed

5 files changed

+72
-0
lines changed

api/fixtures/camps.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ App\Entity\Camp:
5454
addressCity: <city()>
5555
owner: '@admin'
5656
creator: '@admin'
57+
isPublic: true
5758
isPrototype: true
5859
isShared: false
5960
sharedBy: null
@@ -69,6 +70,7 @@ App\Entity\Camp:
6970
addressCity: <city()>
7071
owner: '@admin'
7172
creator: '@admin'
73+
isPublic: true
7274
isPrototype: false
7375
isShared: true
7476
sharedBy: '@admin'
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20251004093025 extends AbstractMigration {
14+
public function getDescription(): string {
15+
return '';
16+
}
17+
18+
public function up(Schema $schema): void {
19+
// this up() migration is auto-generated, please modify it to your needs
20+
$this->addSql('ALTER TABLE camp ADD isPublic BOOLEAN DEFAULT false NOT NULL');
21+
$this->addSql('UPDATE camp SET isPublic = (isShared OR isPrototype)');
22+
$this->addSql('ALTER TABLE camp ADD CONSTRAINT enforce_public_flag CHECK (isPublic = (isShared OR isPrototype))');
23+
$this->addSql('CREATE INDEX IDX_C1944230FADC24C7 ON camp (isPublic)');
24+
$this->addSql(
25+
<<<'EOF'
26+
CREATE OR REPLACE VIEW public.view_user_camps
27+
AS
28+
SELECT CONCAT(u.id, c.id) id, u.id userid, c.id campid
29+
from camp c, "user" u
30+
where c.ispublic = TRUE
31+
union all
32+
select cc.id, cc.userid, cc.campid
33+
from camp_collaboration cc
34+
where cc.status = 'established'
35+
EOF
36+
);
37+
}
38+
39+
public function down(Schema $schema): void {
40+
// this down() migration is auto-generated, please modify it to your needs
41+
$this->addSql(
42+
<<<'EOF'
43+
CREATE OR REPLACE VIEW public.view_user_camps
44+
AS
45+
SELECT CONCAT(u.id, c.id) id, u.id userid, c.id campid
46+
from camp c, "user" u
47+
where c.isprototype = TRUE or c.isshared = TRUE
48+
union all
49+
select cc.id, cc.userid, cc.campid
50+
from camp_collaboration cc
51+
where cc.status = 'established'
52+
EOF
53+
);
54+
$this->addSql('DROP INDEX IDX_C1944230FADC24C7');
55+
$this->addSql('ALTER TABLE camp DROP CONSTRAINT enforce_public_flag');
56+
$this->addSql('ALTER TABLE camp DROP isPublic');
57+
}
58+
}

api/src/Entity/Camp.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
#[ORM\Entity(repositoryClass: CampRepository::class)]
7070
#[ORM\Index(columns: ['isPrototype'])]
7171
#[ORM\Index(columns: ['isShared'])]
72+
#[ORM\Index(columns: ['isPublic'])]
7273
#[ORM\Index(columns: ['updateTime'])] // TODO unclear why this is necessary, but doctrine forgot about this index from BaseEntity...
7374
class Camp extends BaseEntity implements BelongsToCampInterface, CopyFromPrototypeInterface {
7475
public const ITEM_NORMALIZATION_CONTEXT = [
@@ -228,6 +229,14 @@ class Camp extends BaseEntity implements BelongsToCampInterface, CopyFromPrototy
228229
#[ORM\Column(type: 'boolean')]
229230
public bool $isPrototype = false;
230231

232+
/**
233+
* Automatically set to the value (isShared || isPrototype). Used for more efficient
234+
* database filtering operations, since OR queries are very expensive to compute.
235+
* This is only used in the database, and therefore not available on the API.
236+
*/
237+
#[ORM\Column(type: 'boolean', nullable: false, options: ['default' => false])]
238+
public bool $isPublic = false;
239+
231240
/**
232241
* An optional short title for the camp. Can be used in the UI where space is tight. If
233242
* not present, frontends may auto-shorten the title if the shortTitle is not set.

api/src/State/CampCreateProcessor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public function onBefore($data, Operation $operation, array $uriVariables = [],
3434
$user = $this->security->getUser();
3535
$data->creator = $user;
3636
$data->owner = $user;
37+
$data->isPublic = $data->isShared || $data->isPrototype;
3738

3839
// copy from prototype, if given
3940
if (null !== $data->campPrototype) {

api/src/State/CampUpdateProcessor.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public function __construct(
3232
}
3333

3434
public function onBeforeSharingStatusChange(Camp $data): Camp {
35+
$data->isPublic = $data->isShared || $data->isPrototype;
36+
3537
if ($data->isShared) {
3638
$data->sharedSince = new \DateTime('now', new \DateTimeZone('UTC'));
3739

0 commit comments

Comments
 (0)