diff --git a/src/database/migrations/2026_05_10_000001_create_document_translations_and_migrate.php b/src/database/migrations/2026_05_10_000001_create_document_translations_and_migrate.php index 5d678ec..e0d1ffc 100644 --- a/src/database/migrations/2026_05_10_000001_create_document_translations_and_migrate.php +++ b/src/database/migrations/2026_05_10_000001_create_document_translations_and_migrate.php @@ -37,23 +37,28 @@ public function up(): void } // 3. Migrate existing data + // Note: step 4 (ALTER TABLE … DROP INDEX) must remain OUTSIDE this + // transaction because MySQL's ALTER TABLE causes an implicit commit. $defaultLocale = config('app.locale', 'en'); $now = now(); - $rows = DB::table('documents')->get(); - foreach ($rows as $row) { - DB::table('document_translations')->insert([ - 'document_id' => $row->id, - 'locale' => $defaultLocale, - 'title' => $row->title ?? '', - 'content' => $row->content ?? '', - 'rendered_html' => $row->rendered_html, - 'created_by' => $row->created_by ?? null, - 'updated_by' => $row->updated_by ?? null, - 'created_at' => $row->created_at ?? $now, - 'updated_at' => $row->updated_at ?? $now, - ]); - DB::table('documents')->where('id', $row->id)->update(['default_locale' => $defaultLocale]); - } + DB::transaction(function () use ($defaultLocale, $now) { + DB::table('documents')->orderBy('id')->chunkById(500, function ($rows) use ($defaultLocale, $now) { + foreach ($rows as $row) { + DB::table('document_translations')->insert([ + 'document_id' => $row->id, + 'locale' => $defaultLocale, + 'title' => $row->title ?? '', + 'content' => $row->content ?? '', + 'rendered_html' => $row->rendered_html, + 'created_by' => $row->created_by ?? null, + 'updated_by' => $row->updated_by ?? null, + 'created_at' => $row->created_at ?? $now, + 'updated_at' => $row->updated_at ?? $now, + ]); + DB::table('documents')->where('id', $row->id)->update(['default_locale' => $defaultLocale]); + } + }); + }); // 4. Drop the old FULLTEXT index on documents (MySQL only) if (DB::connection()->getDriverName() === 'mysql') { @@ -70,9 +75,13 @@ public function up(): void public function down(): void { - // Re-add columns + // IRREVERSIBLE for non-default-locale translations: only the row matching + // each document's default_locale is restored to the legacy columns; any + // other-locale translations are dropped along with document_translations. + + // Re-add columns (with the title index that up() expects to drop) Schema::table('documents', function (Blueprint $table) { - $table->string('title')->nullable()->after('default_locale'); + $table->string('title')->nullable()->index()->after('default_locale'); $table->text('content')->nullable()->after('title'); $table->text('rendered_html')->nullable()->after('content'); }); diff --git a/src/tests/Feature/DocumentMigrationTest.php b/src/tests/Feature/DocumentMigrationTest.php index ade25e0..17c1957 100644 --- a/src/tests/Feature/DocumentMigrationTest.php +++ b/src/tests/Feature/DocumentMigrationTest.php @@ -2,7 +2,6 @@ namespace Tests\Feature; -use Illuminate\Database\Migrations\Migrator; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; @@ -72,4 +71,44 @@ public function test_document_translations_unique_document_locale(): void 'updated_at' => now(), ]); } + + public function test_existing_documents_data_is_copied_to_translations(): void + { + // Roll the new migration back so the legacy columns exist again + \Illuminate\Support\Facades\Artisan::call('migrate:rollback', ['--step' => 1]); + $this->assertTrue(\Illuminate\Support\Facades\Schema::hasColumn('documents', 'title')); + + // Seed a legacy document row directly + \Illuminate\Support\Facades\DB::table('documents')->insert([ + 'path' => 'Legacy.md', + 'title' => 'Legacy Title', + 'slug' => 'legacy-title', + 'content' => '# Legacy body', + 'rendered_html' => '