<?php
declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

/**
 * Composite index for the queued-task finder (QueuedTaskFinderDb::tasks()).
 *
 * The finder pulls "due" jobs with:
 *
 *   WHERE processed IS NULL AND failed IS NULL AND locked = 0
 *     AND run_after <= :now
 *     AND (sequence > :s OR (sequence = :s AND id > :i))   -- keyset cursor
 *   ORDER BY sequence ASC, id ASC
 *
 * The three equality/IS NULL predicates form the index prefix, collapsing a
 * 10M+ row table down to the small pending set instantly. `(sequence, id)`
 * follows so the ORDER BY and the keyset cursor range are served straight from
 * the index with no filesort — jobs MUST run in sequence order per handler, so
 * sequence leads and id is only the unique tiebreaker. `run_after` sits last
 * so `run_after <= :now` is a cheap index-condition-pushdown residual rather
 * than a table lookup.
 *
 * The pre-existing single-column indexes (processed, locked, run_after) no
 * longer serve this query once this composite exists.
 */
final class AddDueJobIndexToJobQueue extends AbstractMigration
{
    public function change(): void
    {
        $this->table('job_queue')
            ->addIndex(['processed', 'failed', 'locked', 'sequence', 'id', 'run_after'], [
                'name' => 'idx_job_queue_due',
            ])
            ->update();
    }
}
