Laravel Unit Testing Migration fails - laravel

I am trying to run PHPUnit test. I have setup SQLite in :memory for the testing environment. In my setup, I call Artisan::call('migrate') but then I get the following error:
General error: 1 Cannot add a NOT NULL column with default value NULL
(SQL: alter table "admins" add column "title" text not null)
Basically, any migration file that is modifying an existing table returns an error. Why?
Here is the file migration is complaining about:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddTitleToAdminsTable extends Migration {
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::table('admins', function(Blueprint $table)
{
$table->text('title');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('admins', function(Blueprint $table)
{
$table->dropColumn('title');
});
}
}

I did some research and found this post on Stack Overflow.
It appears that SQLite has a problem with columns that are NOT NULL but have no default value. When in the same situation, MySQL just uses an empty string (for varchar) or "0" (for numbers). So it kind of has built in default values.
To solve your problem you can either make the column nullable (only if you want it to be nullable) or define a default.
$table->text('title')->nullable();
$table->text('title')->default('');

Related

#Laravel Migration: Cannot add foreign key constraint in laravel

I'm trying to create foreign keys in Laravel however when I migrate my table using artisan i am thrown the following error:
λ php artisan migrate
Migration table created successfully.
[Illuminate\Database\QueryException]
SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint (SQL: alter table `fees` add constraint `fee
s_fee_type_id_foreign` foreign key (`fee_type_id`) references `feetypes` (`fee_type_id`))
[PDOException]
SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint
My migration code is as so:
fees
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateFeesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('fees', function (Blueprint $table) {
$table->increments('fee_id');
$table->integer('academic_id')->unsigned();
$table->integer('level_id')->unsigned();
$table->integer('fee_type_id');
$table->string('fee_heading', 100)->nullable();
$table->float('amount', 8, 2);
$table->foreign('academic_id')->references('academic_id')->on('academics');
$table->foreign('level_id')->references('level_id')->on('levels');
$table->foreign('fee_type_id')->references('fee_type_id')->on('feetypes');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('fees');
}
}
fesstypes
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateFeetypesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('feetypes', function (Blueprint $table) {
$table->unsignedinteger('fee_type_id');
$table->string('fee_type', 100);
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('feetypes');
}
}
Any ideas as to what I've done wrong, I want to get this right now, as I've got a lot of tables I need to create e.g. Users, Students, Leves, etc. Ideally I want to create tables which hold this data with the foreign keys, i..e fees and feetypes.
Hope someone can help me to get started.
Replace this line:
$table->integer('fee_type_id');
With this:
$table->unsignedInteger('fee_type_id');
In CreateFeesTable Migration.
Check all data types matches for the referenced data types.
public function up()
{
Schema::create('fees', function (Blueprint $table) {
$table->increments('fee_id');
$table->unsignedInteger('academic_id');
$table->unsignedInteger('level_id');
$table->unsignedInteger('fee_type_id');
$table->string('fee_heading', 100)->nullable();
$table->float('amount', 8, 2);
$table->foreign('academic_id')->references('academic_id')->on('academics');
$table->foreign('level_id')->references('level_id')->on('levels');
$table->foreign('fee_type_id')->references('fee_type_id')->on('feetypes');
});
}
check level_id in levels , academic_id in academics ,fee_type_id in feetypes are also unsignedInteger or autoincrement and change your table creating script to above one.
CreateFeesTable.php
change
$table->integer('fee_type_id');
to
$table->unsignedInteger('fee_type_id');
CreateFeetypesTable
change
$table->unsignedinteger('fee_type_id');
to
$table->increments('fee_type_id'); or
$table->integer('fee_type_id');
You must first create an index on the referenced column, i.e. fee_type_id in feetypes table.
MySQL requires indexes on foreign keys and referenced keys so that foreign key checks can be fast and not require a table scan. In the referencing table, there must be an index where the foreign key columns are listed as the first columns in the same order. Such an index is created on the referencing table automatically if it does not exist. This index might be silently dropped later, if you create another index that can be used to enforce the foreign key constraint. index_name, if given, is used as described previously.
From MySQL Reference Manual

SQLite Expecting a string as Index Column when dropping a column

<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class RemoveConstructionDateToOrdersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
if (Schema::hasColumn('orders', 'construction_date')) {
Schema::table('orders', function (Blueprint $table) {
$table->dropColumn('construction_date');
});
}
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('orders', function (Blueprint $table) {
$table->date('construction_date');
});
}
}
When I am migrating this laravel migration on a sqlite database, I am getting
the following error.
InvalidArgumentException: Expecting a string as Index Column
I found where the problem is after hours of poking around. Looks like the doctrine/dbal package cannot rename/handle/drop?! SQLite columns that already have an index
So my solution was to NOT make any indexes on columns that get later renamed in the migrations
Of course we do want the indexes to exist in MySQL so here's my solution to it.
Schema::table('comments', function (Blueprint $table) {
$table->string('commentable_type', 100)->nullable()->after('id');
//only add index if the connection is mysql
if(config('DB_CONNECTION') === 'mysql'){
$table->index('commentable_type');
}
});
So later when you try to rename the column and using SQLite it works.

Change tables structures with migration without losing data?

I have read several threads about this but still not exactly my problem.
So in the users table I have a column called role, which is enum type and has two available values: 1 and 2. I set 2 as the default one. Now I want to change it to 1, for example. I created a new migration, ran php artisan migrate and encounter this error:
[Illuminate\DatabaseQueryException]
SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'role' (SQL: alter table `u
sers` add `role` enum('1', '2') not null default '1')
[PDOException]
SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'role'
Here is code in my CreateUsersTable migration file:
$table->enum('role', ['1', '2'])->default('2');
And I did the same in the new UpdateUsersTable migration file:
$table->enum('role', ['1', '2'])->default('1');
And by the way I can not use php artisan migrate:refresh because it will delete all my data. Where am I doing wrong?
$table->enum('role', ['1', '2'])->default('1')->change();
<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Migrations\Migration;
class UpdateUsersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
DB::statement('ALTER TABLE `users` MODIFY `role` DEFAULT 1;');
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
DB::statement('ALTER TABLE `users` MODIFY `role` DEFAULT 2;');
}
}
Adapted from this answer to another question
In order to do this you have to add ->nullable() or ->default() to every field you add to the migration file.
create a new migration file
php artisan make:migration add_role_to_users_table --table=users
open the created migration file (database\migrations\2021_12_01_050851-add_role_tables.php) and add below code.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddRoleToUsers extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->enum('role', ['1', '2'])->default('1')->comment('1 - admin, 2 - normal'); //added
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role'); //added
});
}
}
?>
Now migration refresh
php artisan migrate:refresh --path=/database/migrations/2021_12_01_050851-add_role_tables.php

Laravel migrations: dropping a specific table

Is there any way/laravel-command to drop a specific table from the production server?
Set up a migration.
Run this command to set up a migration:
php artisan make:migration drop_my_table
Then you can structure your migration like this:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class DropMyTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
// drop the table
Schema::dropIfExists('my_table');
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
// create the table
Schema::create('my_table', function (Blueprint $table) {
$table->increments('id');
// .. other columns
$table->timestamps();
});
}
}
You can of course just drop and not check for existence:
Schema::drop('my_table');
Read further in the docs here:
https://laravel.com/docs/5.2/migrations#writing-migrations
You may also have to consider dropping any existing foreign keys/indexes, for example if you wanted to drop a primary key:
public function up()
{
Schema::table('my_table', function ($table) {
$table->dropPrimary('my_table_id_primary');
});
Schema::dropIfExists('my_table');
}
More in the docs in dropping indexes etc here:
https://laravel.com/docs/5.2/migrations#dropping-indexes

Setting Default Value for Primary Key in Laravel Migration for sqlite

I've created the database migration below. What I'd like to do is create an accounts table with an id as a primary key. However, I don't want the key to autoincrement starting at 1. Rather, I'd like it to autoincrement starting at 800500.
Is there a way to set the default value of a primary key like this?
I'm currently using Laravel v4.2.11 and sqlite v3.8.3.
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateAccountsTable extends Migration {
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('accounts', function(Blueprint $table)
{
$table->increments('id')->unsigned()->default(800500);
$table->string('name', 100);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('accounts');
}
}
The default method in the schema builder
Declare(s) a default value for a column
If you need the increment to start at a given value take a look at this answer.
You'd add the query to the migration:
public function up()
{
Schema::create('accounts', function(Blueprint $table)
{
$table->increments('id');
$table->string('name', 100);
$table->timestamps();
});
DB::statement("UPDATE SQLITE_SEQUENCE SET seq = 800500 WHERE name = 'accounts'");
}
There is no 'laravel' way to do this.

Resources