Tuesday, March 7, 2017

Foreign key constraint fails when rolling back migrations in laravel

I am in a very unique problem, but I am sure other people must have faced something similar in laravel.

So my app is in production now and real data is in the database so I cannot edit old migration files anymore. What I can do is to create new migration, so that is what I am doing.

The user table in my database is not going to be used anymore, another database's user table will be used. So I need to remove the user table and also remove all the foreign keys I set for the same in other tables so I created a migration like:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class DropUserIdForeignKeyConstraintFromTables extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        \DB::statement('ALTER TABLE accessible_by_role DROP FOREIGN KEY accessible_by_role_role_id_foreign');

        \DB::statement('ALTER TABLE accessible_by_user DROP FOREIGN KEY accessible_by_user_user_id_foreign');
        \DB::statement('ALTER TABLE document_term DROP FOREIGN KEY document_term_user_id_foreign');
        \DB::statement('ALTER TABLE file DROP FOREIGN KEY file_user_id_foreign');
        \DB::statement('ALTER TABLE mail DROP FOREIGN KEY mail_from_user_id_foreign');
        \DB::statement('ALTER TABLE mail DROP FOREIGN KEY mail_to_user_id_foreign');
        \DB::statement('ALTER TABLE `order` DROP FOREIGN KEY order_user_id_foreign');
        \DB::statement('ALTER TABLE report DROP FOREIGN KEY report_user_id_foreign');
        \DB::statement('ALTER TABLE role_user DROP FOREIGN KEY role_user_user_id_foreign');
        \DB::statement('ALTER TABLE termable DROP FOREIGN KEY termable_user_id_foreign');

        \DB::statement('ALTER TABLE document_term DROP KEY document_term_user_id_foreign');
        \DB::statement('ALTER TABLE file DROP KEY file_user_id_foreign');
        \DB::statement('ALTER TABLE mail DROP KEY mail_from_user_id_foreign');
        \DB::statement('ALTER TABLE mail DROP KEY mail_to_user_id_foreign');
        \DB::statement('ALTER TABLE `order` DROP KEY order_user_id_foreign');
        \DB::statement('ALTER TABLE report DROP KEY report_user_id_foreign');
        \DB::statement('ALTER TABLE role_user DROP KEY role_user_user_id_foreign');
        \DB::statement('ALTER TABLE termable DROP KEY termable_user_id_foreign');
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('accessible_by_user', function (Blueprint $table) {
            $table->foreign('user_id')->references('id')->on('user')->onDelete('cascade');
        });

        Schema::table('document_term', function (Blueprint $table) {
            $table->foreign('user_id')->references('id')->on('user')->onDelete('cascade');
        });

        Schema::table('file', function (Blueprint $table) {
            $table->foreign('user_id')->references('id')->on('user')->onDelete('cascade');
        });

        Schema::table('mail', function (Blueprint $table) {
            $table->foreign('from_user_id')->references('id')->on('user')->onDelete('cascade');
            $table->foreign('to_user_id')->references('id')->on('user')->onDelete('cascade');
        });

        Schema::table('order', function (Blueprint $table) {
            $table->foreign('user_id')->references('id')->on('user')->onDelete('cascade');
        });

        Schema::table('report', function (Blueprint $table) {
            $table->foreign('user_id')->references('id')->on('user')->onDelete('cascade');
        });

        Schema::table('role_user', function (Blueprint $table) {
            $table->foreign('user_id')->references('id')->on('user')->onDelete('cascade');
        });

        Schema::table('termable', function (Blueprint $table) {
            $table->foreign('user_id')->references('id')->on('user')->onDelete('cascade');
        });
    }
}

Now there is also another migration right after this one which drops the user table:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class DropUserTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::drop('user');
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::create('user', function (Blueprint $table) {
            $table->increments('id');
            $table->string('first_name')->comment('First name of user');
            $table->string('last_name')->comment('Last name of user');
            $table->string('email')->unique()->comment('Unique email address');
            $table->string('password')->comment('Password used for authentication');
            $table->string('reset_password')->comment('Token for resetting forgotten passwords');
            $table->boolean('inactive')->comment('True if user is frozen for some reason');
            $table->string('mobile_number')->comment('Mobile phone number');
            $table->string('mobile_code')->comment('As an added security factor, the user need to add this code for authentication (sent by sms)');
            $table->string('mobile_timer')->comment('');
            $table->string('street');
            $table->string('country', 2);
            $table->string('city');
            $table->string('postal_code');
            $table->softDeletes();
            $table->timestamps();
        });
    }
}

So what happens is that even though the foreign key constrains are removed, the user id from the other database still comes in the records, for example in the file table when the data is populated by the users.

When I try to rollback, the user table is created again and laravel tries to put back the foreign keys back too, but the user_id field, let's say, in the file table has values in it already but those don't exist in the user table which was just created on the rollback and mysql fails to put the foreign key constraint there. I hope I am making sense since this is a tricky problem to explain.

One solution I can think of is not put back the foreign keys in the down method and not let them come back again so that it does not fail. Has anyone else experienced a situation like this?



via Rohan

Advertisement