<?php declare(strict_types = 1);

namespace MailPoet\Migrations\Db;

if (!defined('ABSPATH')) exit;


use MailPoet\Entities\SubscriberEntity;
use MailPoet\Migrator\DbMigration;

class Migration_20260609_120000_Db extends DbMigration {
  public function run(): void {
    $subscribersTable = $this->getTableName(SubscriberEntity::class);

    // Index (deleted_at, created_at) so the subscribers listing's default view
    // (non-trashed, newest first) reads its rows straight from the index:
    // `deleted_at IS NULL` pins the first column, so entries come out ordered
    // by created_at — and by id within ties, since InnoDB appends the primary
    // key to secondary indexes. Without it the query has no usable index at
    // all (every other index leads with another column) and degrades to a
    // full-table scan plus a filesort of every non-trashed subscriber.
    //
    // A single-column created_at index would serve the same query slightly
    // worse (it scans trashed rows too), but the important difference shows
    // under `deleted_at IS NULL` filtering: a single-column index is then
    // fully bound, which qualifies it as a rowid-ordered scan that the
    // optimizer can combine into an index-merge intersect with other
    // status/deleted_at indexes — a plan that materializes and filesorts
    // millions of rows on large sites. With created_at as an unbound second
    // column, this index never qualifies, so that plan cannot be built from
    // it. The deleted_at prefix also covers Trash counts
    // (`deleted_at IS NOT NULL`) as an index-only range scan.
    if (!$this->indexExists($subscribersTable, 'deleted_at_created')) {
      $this->connection->executeQuery(
        "ALTER TABLE `{$subscribersTable}`
          ADD INDEX `deleted_at_created` (`deleted_at`, `created_at`)"
      );
    }
  }
}
