33

Why am I getting this warning when using the SQLite driver? I have no problems with the MySQL driver but SQLite is throwing this error.

It does not make sense to me since I understood the seeding happens after all the migrations are completed so why is it complaining about this issue which would only arise if data was already present in the database.

My two migrations are

FIRST MIGRATION

  public function up() {
    Schema::create('users', function($table) {
      $table->increments('id');
      $table->string('username');
      $table->string('email');
      $table->string('password');
    });
  } 

SECOND MIGRATION

public function up() {
    Schema::table('users', function(Blueprint $table) {
        $table->date('birthday')->after('id');
        $table->string('last_name')->after('id');
        $table->string('first_name')->after('id');
    });
}

ERROR

Exception: SQLSTATE[HY000]: General error: 1 Cannot add a NOT NULL column with default value NULL (SQL: alter table "users" add column "birthday" date not null)

9 Answers 9

37

It looks like this is a SQLite oddity. According to a Laracast forum thread about the same issue:

When adding a table from scratch, you can specify NOT NULL. However, you can't do this when adding a column. SQLite's specification says you have to have a default for this, which is a poor choice.

Looking further to the SQLite ALTER TABLE docs, I found:

If a NOT NULL constraint is specified, then the column must have a default value other than NULL.

I suppose in the world of SQLite, not providing a default value is the same thing as saying the default value should be NULL (as opposed to meaning there is no default value for this non-nullable column, so a value must be provided for it on each insert).

It seems SQLite simply leaves you in a bad state if you need to add a non-nullable column to an existing table, which column should also not have a default value.

Sign up to request clarification or add additional context in comments.

1 Comment

PDO should had used implicit conversions.
22

A workaround I've used successfully is to check which database driver is being used and slightly modify the migration for SQLite.

For example:

class MyMigration extends Migration
{
    public function up()
    {
        $driver = Schema::connection($this->getConnection())->getConnection()->getDriverName();

        Schema::table('invoices', function (Blueprint $table) use ($driver) {
            $table->string('stripe_invoice')->nullable()->change();

            if ($driver === 'sqlite') {
                $table->string('stripe_invoice_number')->default('');
            } else {
                $table->string('stripe_invoice_number')->after('stripe_invoice');
            }
        });
    }
}

1 Comment

Thanks, I like this solution for in-memory testing.
16
+50

If you don't want the column to be nullable - then you need to let Laravel know what the default should be.

One option is an empty string "" like this

public function up() {
    Schema::create('users', function($table) {
      $table->date('birthday')->after('id')->default('');
      $table->string('last_name')->after('id')->default('');
      $table->string('first_name')->after('id')->default('');

    });
  } 

6 Comments

@Fabrizio - does that help at all?
it might break up your code where you check those variables agains null with is_null or else, since "" would mean the column is not null but an empty string.
This makes the code run, but completely beats the purpose of the NOT NULL check in the database.
Actually, this is wrong, since in MySQL you can set a colum to be NOT NULL without specifiyng a default value: it can be a desired behaviour if you want to force the user to set the value.
@fudo - the OPs original question is about SQLite - not mySQL.
|
9

All the folks solutions are good, but I wanted to find a reusable and readable way to do this, so I made a trait and hope it can help you remove some boilerplate codes out of your migrations.

The method is $this->databaseDriverIs("driver_name_here");.

Here is how I use it in a typical table creation migration:

<?php

use App\Traits\WithDatabaseDriver;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCountryTable extends Migration
{
  use WithDatabaseDriver;

  public function up()
  {
    Schema::create("country", function (Blueprint $table) {
      $table->increments("id");
      $table->string("name");

      $table->unique("name", "unique_country_name");
    });

    Schema::table("continent", function (Blueprint $table) {
      $column = $table->unsignedInteger("countryId");

      // This is where we apply the "fix" if 
      // the driver is SQLite
      if ($this->databaseDriverIs("sqlite")) {
        $column->nullable();
      }

      $table->foreign("countryId")->references("id")->on("country");
    });
  }

  public function down()
  {
    Schema::dropIfExists("country");
  }
}

And this is the trait that does all the job:

<?php

namespace App\Traits;

trait WithDatabaseDriver
{
  /**
   * @var string
   */
  private $driver;

  public function __construct()
  {
    $this->driver = config("database.default");
  }

  public function databaseDriverIs(string $driver): bool
  {
    return $this->driver === $driver;
  }
}

1 Comment

If your project uses many database: $this->driver = DB::connection()->getPDO()->getAttribute(PDO::ATTR_DRIVER_NAME); or $this->driver = Schema::connection($this->getConnection())->getConnection()->getDriverName();
4

I'm not familiar with Laravel, but apparently the use of the after method to specify the order of columns appears to specifically mention ONLY MySQL (Laravel) and a discussion (GitHub) seems to point to difficulties in it's use with SQLite. It may likely be incompatible because its function: "...use the after method to specify the order of columns" design runs up against the limitation in SQLite's documentation (SQLite) for adding columns... which reads: "The new column is always appended to the end of the list of existing columns." I can't say whether assigning default values with ->default($value) can get you by or not.

2 Comments

The after method call is just ignored on SQLite. I'm having the same problem myself and it seems like a limitation in the SQLite adapter - when creating a table for the first time, it's fine to have some columns be not nullable, but when altering and adding a column, even though the table is empty, for some reason it balks. Kind of annoying when the only reason I have SQLite is to do in-memory acceptance tests
Still having this issue. Have you found a workaround for this?
2

you have to add

->nullable()

for the columns that may have a null value

1 Comment

or ->default('');
1

Another work around for this issue is to first create the fields as nullable and then later change the fields to be not null. So for example in this case we will do something like the following:

public function up() {
    // Create fields first as nullable
    Schema::table('users', function(Blueprint $table) {
        $table->date('birthday')->after('id')->nullable();
        $table->string('last_name')->after('id')->nullable();
        $table->string('first_name')->after('id')->nullable();
    });

    // Either truncate existing records or assign a value to new fields
    if (true) {
        DB::table('users')->truncate();
    } else {
        DB::table('users')->update([
            'birthday' => '2019-05-01',
            'last_name' => 'last name',
            'first_name' => 'first name',
        ]);
    }

    // Change the fields to not be null
    Schema::table('users', function(Blueprint $table) {
        $table->date('birthday')->nullable(false)->change();
        $table->string('last_name')->nullable(false)->change();
        $table->string('first_name')->nullable(false)->change();
    });
}

Comments

0

In my case interestingly, I had to run php artisan config:cache and it actually started working for my SQLite database (source).

I'd run php artisan config:clear prior.

Quite odd, but it did the job.

Comments

-1

I did as people explained and it worked, just make your foreign keys nullables in your migration

public function up()
{
    Schema::table('products', function (Blueprint $table) {
        $table->foreignId('category_id')
            ->nullable()
            ->constrained();
    });
}

1 Comment

make foreign key nullable is not a better solution it meets your use cases but not mine recommended solution

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.