Migration shopping_items: idempotent (Duplikat-Spalten, MySQL-Check, Repair)

Made-with: Cursor
This commit is contained in:
Stefan Zwischenbrugger 2026-03-29 21:06:53 +02:00
parent 7e01043c99
commit 635a0ec28a
2 changed files with 183 additions and 26 deletions

View File

@ -1,6 +1,7 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\QueryException;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
@ -9,15 +10,43 @@ return new class extends Migration
{
public function up(): void
{
Schema::table('shopping_items', function (Blueprint $table) {
$table->unsignedBigInteger('shopping_list_id')->nullable();
$table->unsignedBigInteger('created_by')->nullable();
});
if (! Schema::hasTable('shopping_items')) {
return;
}
$this->addColumnUnlessExists('shopping_list_id');
$this->addColumnUnlessExists('created_by');
$userIds = DB::table('users')->pluck('id');
$map = [];
foreach ($userIds as $userId) {
$pivot = DB::table('shopping_list_user')
->where('user_id', $userId)
->orderBy('shopping_list_id')
->first();
if ($pivot !== null) {
$map[$userId] = $pivot->shopping_list_id;
continue;
}
$owned = DB::table('shopping_lists')->where('owner_id', $userId)->orderBy('id')->first();
if ($owned !== null) {
$map[$userId] = $owned->id;
if (! DB::table('shopping_list_user')->where('shopping_list_id', $owned->id)->where('user_id', $userId)->exists()) {
DB::table('shopping_list_user')->insert([
'shopping_list_id' => $owned->id,
'user_id' => $userId,
'created_at' => now(),
'updated_at' => now(),
]);
}
continue;
}
$map[$userId] = DB::table('shopping_lists')->insertGetId([
'owner_id' => $userId,
'name' => 'Einkaufsliste',
@ -32,33 +61,111 @@ return new class extends Migration
]);
}
$items = DB::table('shopping_items')->select('id', 'user_id')->get();
foreach ($items as $item) {
if (! isset($map[$item->user_id])) {
continue;
if (Schema::hasColumn('shopping_items', 'user_id')) {
$items = DB::table('shopping_items')->select('id', 'user_id')->get();
foreach ($items as $item) {
if (! isset($map[$item->user_id])) {
continue;
}
DB::table('shopping_items')->where('id', $item->id)->update([
'shopping_list_id' => $map[$item->user_id],
'created_by' => $item->user_id,
]);
}
DB::table('shopping_items')->where('id', $item->id)->update([
'shopping_list_id' => $map[$item->user_id],
'created_by' => $item->user_id,
]);
Schema::table('shopping_items', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropColumn('user_id');
});
}
Schema::table('shopping_items', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropColumn('user_id');
});
if (Schema::getConnection()->getDriverName() === 'mysql') {
$col = DB::select("SHOW COLUMNS FROM shopping_items WHERE Field = 'shopping_list_id'");
if (! empty($col) && ($col[0]->Null ?? '') === 'YES') {
Schema::table('shopping_items', function (Blueprint $table) {
$table->unsignedBigInteger('shopping_list_id')->nullable(false)->change();
});
}
} else {
Schema::table('shopping_items', function (Blueprint $table) {
$table->unsignedBigInteger('shopping_list_id')->nullable(false)->change();
});
}
Schema::table('shopping_items', function (Blueprint $table) {
$table->unsignedBigInteger('shopping_list_id')->nullable(false)->change();
$table->unsignedBigInteger('created_by')->nullable(false)->change();
});
try {
Schema::table('shopping_items', function (Blueprint $table) {
$table->foreign('shopping_list_id')->references('id')->on('shopping_lists')->cascadeOnDelete();
});
} catch (\Throwable) {
}
Schema::table('shopping_items', function (Blueprint $table) {
$table->foreign('shopping_list_id')->references('id')->on('shopping_lists')->cascadeOnDelete();
$table->foreign('created_by')->references('id')->on('users')->nullOnDelete();
$table->index(['shopping_list_id', 'is_done']);
$table->index(['shopping_list_id', 'store_id']);
});
try {
Schema::table('shopping_items', function (Blueprint $table) {
$table->foreign('created_by')->references('id')->on('users')->nullOnDelete();
});
} catch (\Throwable) {
}
try {
Schema::table('shopping_items', function (Blueprint $table) {
$table->index(['shopping_list_id', 'is_done']);
});
} catch (\Throwable) {
}
try {
Schema::table('shopping_items', function (Blueprint $table) {
$table->index(['shopping_list_id', 'store_id']);
});
} catch (\Throwable) {
}
}
private function addColumnUnlessExists(string $column): void
{
if ($this->columnExistsMySql('shopping_items', $column)) {
return;
}
if (Schema::hasColumn('shopping_items', $column)) {
return;
}
try {
Schema::table('shopping_items', function (Blueprint $table) use ($column) {
$table->unsignedBigInteger($column)->nullable();
});
} catch (QueryException $e) {
if ($this->isDuplicateColumnError($e)) {
return;
}
throw $e;
}
}
private function columnExistsMySql(string $table, string $column): bool
{
if (Schema::getConnection()->getDriverName() !== 'mysql') {
return false;
}
$db = Schema::getConnection()->getDatabaseName();
$n = DB::selectOne(
'SELECT COUNT(*) AS c FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?',
[$db, $table, $column]
);
return $n !== null && (int) $n->c > 0;
}
private function isDuplicateColumnError(QueryException $e): bool
{
if ($e->getCode() === '42S21') {
return true;
}
return str_contains($e->getMessage(), 'Duplicate column');
}
public function down(): void

View File

@ -0,0 +1,50 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
/**
* Repariert fehlgeschlagene Laeufe von 2026_03_29_100100 (MySQL errno 150):
* NOT NULL + ON DELETE SET NULL ist ungueltig. Nur ausfuehren wenn created_by noch NOT NULL ist.
*/
return new class extends Migration
{
public function up(): void
{
if (! Schema::hasTable('shopping_items') || ! Schema::hasColumn('shopping_items', 'created_by')) {
return;
}
if (Schema::getConnection()->getDriverName() !== 'mysql') {
return;
}
$col = DB::select("SHOW COLUMNS FROM shopping_items WHERE Field = 'created_by'");
if (empty($col) || ($col[0]->Null ?? '') === 'YES') {
return;
}
try {
Schema::table('shopping_items', function (Blueprint $table) {
$table->dropForeign(['created_by']);
});
} catch (\Throwable) {
}
DB::statement('ALTER TABLE shopping_items MODIFY created_by BIGINT UNSIGNED NULL');
try {
Schema::table('shopping_items', function (Blueprint $table) {
$table->foreign('created_by')->references('id')->on('users')->nullOnDelete();
});
} catch (\Throwable) {
}
}
public function down(): void
{
//
}
};