diff --git a/database/migrations/2026_07_02_133321_add_user_source_created_at_index_to_transactions_table.php b/database/migrations/2026_07_02_133321_add_user_source_created_at_index_to_transactions_table.php new file mode 100644 index 00000000..373f5c9e --- /dev/null +++ b/database/migrations/2026_07_02_133321_add_user_source_created_at_index_to_transactions_table.php @@ -0,0 +1,30 @@ + ?). The existing + * indexes only lead with user_id via transaction_date, so that query scans + * every one of the user's rows to filter on source + created_at + * (PHP-LARAVEL-3X). This composite index matches the two equalities and the + * range directly. + */ + public function up(): void + { + Schema::table('transactions', function (Blueprint $table) { + $table->index(['user_id', 'source', 'created_at'], 'idx_transactions_user_source_created'); + }); + } + + public function down(): void + { + Schema::table('transactions', function (Blueprint $table) { + $table->dropIndex('idx_transactions_user_source_created'); + }); + } +}; diff --git a/tests/Feature/OpenBanking/DailyEmailTransactionsIndexTest.php b/tests/Feature/OpenBanking/DailyEmailTransactionsIndexTest.php new file mode 100644 index 00000000..03c3b021 --- /dev/null +++ b/tests/Feature/OpenBanking/DailyEmailTransactionsIndexTest.php @@ -0,0 +1,14 @@ +first(fn (array $index): bool => $index['columns'] === ['user_id', 'source', 'created_at'] + ); + + expect($match)->not->toBeNull( + 'Expected a (user_id, source, created_at) index on transactions for the daily email query.' + ); +});