perf(db): composite index on wallet_transactions(user_id, type) (M14)

Helps the per-type SUM balance query. M13 (service_items.section_id) and M15
(clinic_doctor_invitations.doctor_id) were false positives — both columns carry
a FK and are therefore auto-indexed by InnoDB; verified against the live schema.

Structural regression: InfraSmokeTest::testWalletUserTypeIndexExists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-28 20:28:24 +03:30
co-authored by Claude Opus 4.8
parent 670cef24f4
commit 447482c2ea
4 changed files with 47 additions and 3 deletions
+3 -3
View File
@@ -66,9 +66,9 @@ _None outstanding._
| M10 | Unbounded list: `listMine` settlements `findByUser` no limit | src/Settlement/Controller/SettlementController.php:193 | perf-pagination | GET settlement list → paginate |
| M11 | Unbounded list: admin `pendingComments` `findPending` no limit | src/Rating/Controller/RatingController.php:350 | perf-pagination | admin moderation large backlog → paginate |
| M12 | Unbounded list: public `listComments` per-doctor no limit | src/Rating/Controller/RatingController.php:270 | perf-pagination | popular doctor comments → paginate |
| M13 | Missing index: `ServiceItem.section_id` FK fully unindexed (entity has zero indexes) | src/ClinicService/Entity/ServiceItem.php:23-24 | perf-index | EXPLAIN findBySection → no full scan |
| M14 | Missing index: `WalletTransaction(user_id, type)` composite for per-type SUM | src/Settlement/Entity/WalletTransaction.php:38-39 | perf-index | EXPLAIN balance SUM query |
| M15 | Missing index: `ClinicDoctorInvitation.doctor_id` FK unindexed | src/ClinicInvitation/Entity/ClinicDoctorInvitation.php:41-42 | perf-index | EXPLAIN findPendingByDoctor |
| ⚠️M13 | Missing index: `ServiceItem.section_id` FK fully unindexed (entity has zero indexes) | src/ClinicService/Entity/ServiceItem.php:23-24 | perf-index | EXPLAIN findBySection → no full scan |
| M14 | Missing index: `WalletTransaction(user_id, type)` composite for per-type SUM | src/Settlement/Entity/WalletTransaction.php:38-39 | perf-index | **DONE** — added composite `idx_wallet_user_type` + migration. `InfraSmokeTest::testWalletUserTypeIndexExists`. |
| ⚠️M15 | Missing index: `ClinicDoctorInvitation.doctor_id` FK unindexed | src/ClinicInvitation/Entity/ClinicDoctorInvitation.php:41-42 | perf-index | **FALSE POSITIVE**`doctor_id` has a FK → auto-indexed (IDX_26DCCFEB87F4FB17 exists). No change. |
| M16 | `User.email` and `User.nationalCode` not unique → duplicate identities | src/Auth/Entity/User.php:31-32,37-38 | db-unique | Insert two users same email/national_code → 2nd rejected. ⚠️ check dup data first |
| M17 | `Payment.gatewayToken` not unique | src/Payment/Entity/Payment.php:58-59 | db-unique | Two payments same gateway_token → rejected |
| M18 | `DateOverride` no UNIQUE `(doctor_id, date)` → ambiguous schedule | src/Appointment/Entity/DateOverride.php:12 | db-unique | Two overrides same doctor+date → rejected |
+31
View File
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260628165710 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE INDEX idx_wallet_user_type ON wallet_transactions (user_id, type)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX idx_wallet_user_type ON wallet_transactions');
}
}
@@ -11,6 +11,7 @@ use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: WalletTransactionRepository::class)]
#[ORM\Table(name: 'wallet_transactions')]
#[ORM\Index(columns: ['user_id', 'created_at'], name: 'idx_wallet_user_date')]
#[ORM\Index(columns: ['user_id', 'type'], name: 'idx_wallet_user_type')]
class WalletTransaction
{
public const TYPE_CREDIT = 'credit';
+12
View File
@@ -31,4 +31,16 @@ class InfraSmokeTest extends ApiTestCase
$this->assertTrue($body['success']);
$this->assertSame($user->getUuid(), $body['data']['uuid']);
}
public function testWalletUserTypeIndexExists(): void
{
$conn = $this->em->getConnection();
$count = (int) $conn->fetchOne(
"SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'wallet_transactions'
AND INDEX_NAME = 'idx_wallet_user_type'"
);
$this->assertGreaterThan(0, $count, 'composite index idx_wallet_user_type is missing');
}
}