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

36

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.

1
  • 1
    PDO should had used implicit conversions. Commented Dec 15, 2015 at 10:03
21

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
  • Thanks, I like this solution for in-memory testing.
    – benjivm
    Commented Jan 6, 2020 at 19:50
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
  • @Fabrizio - does that help at all?
    – Laurence
    Commented Feb 16, 2015 at 5:09
  • 3
    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.
    – spetsnaz
    Commented Jan 14, 2017 at 19:13
  • This makes the code run, but completely beats the purpose of the NOT NULL check in the database.
    – i.amniels
    Commented Feb 24, 2021 at 16:03
  • 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
    Commented May 26, 2021 at 16:22
  • @fudo - the OPs original question is about SQLite - not mySQL.
    – Laurence
    Commented Jun 2, 2021 at 6:44
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
  • 1
    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();
    – OLIVIERS
    Commented Mar 18, 2021 at 16:40
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
  • 7
    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
    – michel-slm
    Commented Feb 19, 2014 at 10:02
  • 1
    Still having this issue. Have you found a workaround for this?
    – Jeff
    Commented Oct 10, 2014 at 17:57
1

you have to add

->nullable()

for the columns that may have a null value

1
  • or ->default(''); Commented Dec 27, 2018 at 3:40
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();
    });
}
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.

-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
  • 1
    make foreign key nullable is not a better solution it meets your use cases but not mine recommended solution Commented Jun 6, 2021 at 4:54

Not the answer you're looking for? Browse other questions tagged or ask your own question.