PR-2 verified that Spatie's activitylog default migration creates the
composite indexes RFC-WS-7 §3.14 / addendum D-06 require — via
nullableMorphs('subject') and nullableMorphs('causer'), which emit
indexes named `subject` on (subject_type, subject_id) and `causer` on
(causer_type, causer_id).
This test queries information_schema.STATISTICS and fails if either
composite is missing, regardless of the index name. It guards against
silent regression when:
- A future Spatie major release changes nullableMorphs semantics.
- A developer rewrites the activity_log migration without preserving
the morph indexes.
- A schema-dump regeneration drops them.
Test count 1539 to 1541. Larastan clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
93 lines
2.8 KiB
PHP
93 lines
2.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Database;
|
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Regression guard for RFC-WS-7 §3.14 / addendum D-06 composite indexes
|
|
* on the activity_log table.
|
|
*
|
|
* Spatie's activitylog default migration calls nullableMorphs('subject')
|
|
* and nullableMorphs('causer'), which create composite indexes named
|
|
* `subject` on (subject_type, subject_id) and `causer` on
|
|
* (causer_type, causer_id). RFC-WS-7 §3.14 / addendum D-06 require
|
|
* exactly these indexes for query planner support on activity_log
|
|
* lookups by morph subject/causer; PR-2 verified they already exist
|
|
* via information_schema.
|
|
*
|
|
* This test fails when:
|
|
* - A future Spatie major release changes nullableMorphs() semantics.
|
|
* - A developer rewrites the activity_log migration without keeping
|
|
* the morph indexes.
|
|
* - A new schema-dump regeneration silently drops them.
|
|
*/
|
|
final class ActivityLogIndexesTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_subject_composite_index_exists(): void
|
|
{
|
|
$this->assertCompositeIndexExists(
|
|
table: 'activity_log',
|
|
columns: ['subject_type', 'subject_id'],
|
|
description: 'subject composite index (RFC-WS-7 §3.14 / D-06)',
|
|
);
|
|
}
|
|
|
|
public function test_causer_composite_index_exists(): void
|
|
{
|
|
$this->assertCompositeIndexExists(
|
|
table: 'activity_log',
|
|
columns: ['causer_type', 'causer_id'],
|
|
description: 'causer composite index (RFC-WS-7 §3.14 / D-06)',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param list<string> $columns
|
|
*/
|
|
private function assertCompositeIndexExists(string $table, array $columns, string $description): void
|
|
{
|
|
$database = config('database.connections.mysql.database');
|
|
|
|
$rows = DB::select(
|
|
'SELECT INDEX_NAME, COLUMN_NAME, SEQ_IN_INDEX
|
|
FROM information_schema.STATISTICS
|
|
WHERE TABLE_SCHEMA = ?
|
|
AND TABLE_NAME = ?
|
|
ORDER BY INDEX_NAME, SEQ_IN_INDEX',
|
|
[$database, $table],
|
|
);
|
|
|
|
$indexColumns = [];
|
|
foreach ($rows as $row) {
|
|
$indexColumns[$row->INDEX_NAME][(int) $row->SEQ_IN_INDEX] = $row->COLUMN_NAME;
|
|
}
|
|
|
|
$found = false;
|
|
foreach ($indexColumns as $sequence) {
|
|
ksort($sequence);
|
|
if (array_values($sequence) === $columns) {
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$this->assertTrue(
|
|
$found,
|
|
sprintf(
|
|
'Expected composite index on %s(%s) — %s. Found indexes: %s',
|
|
$table,
|
|
implode(', ', $columns),
|
|
$description,
|
|
json_encode($indexColumns, JSON_PRETTY_PRINT),
|
|
),
|
|
);
|
|
}
|
|
}
|