Add Not Nullable column to laravel migration with existing DB data - php

I have to add a new column to an existing table that already has data in it and running into a bit of a hiccup.
How can I add this column? Its going to be a Not Nullable field. I'm ok with populating it with default data for right now and going back and updating later. so if we need to drop constraints while adding. I'm assuming I'm going to need to utilize straight SQL queries.
Make this work w/ PHPUnit and SQLite, currently I'm getting an error SQLSTATE[HY000]: General error: 1 Cannot add a NOT NULL column with default value NULL (SQL: alter table "tracks" add column "short_description" text not null)
How would I modify this migration?
public function up()
{
Schema::table('tracks', function(Blueprint $table)
{
$table->text('short_description')->after('description');
});
}

You have to set default value:
public function up()
{
Schema::table('tracks', function(Blueprint $table)
{
$table->text('short_description')->after('description')->default('default_value');
});
}

Related

Composite constraint where deleted_at is NULL

I need to make a composite constraint in Laravel, which only consider non soft-deleted rows. So if the entity was deleted, the deleted_at is not null and data_1, data_2, data_3, data_4, data_5 can be inserted again.
Best I could come up with is this (table and column names were defined in previous migrations):
public function up()
{
Schema::table('table_name', function (Blueprint $table) {
$table->unique(['data_1', 'data_2', 'data_3', 'data_4', 'data_5'])
->whereNull('deleted_at');
});
}
But it doesn't work. The constraint applies even if deleted_at is not null.
I've been looking through other post with similar issues but their solutions have different approaches non related with a migration.
Turns out that making a constraint wasn't the correct approach for the problem.
A unique index allows to make a composition of columns unique, and also admit conditions.
In raw sql statement it is:
public function up()
{
DB::statement('CREATE UNIQUE INDEX table_unique_preferredNameOfIndex ON schema.table
USING btree (data_1, data_2, data_3, data_4, data_5) WHERE (deleted_at IS NULL)');
}
Not strictly the same but it gets the job done.
If a rollback is needed, just drop the index:
public function down()
{
DB::statement('DROP INDEX schema.table_unique_preferredNameOfIndex');
}

How to add a NOT NULLABLE column with a foreign key to an existing table that already contains data?

An example for this problem:
Imagine you have a table projects. The table projects already
contains data. Now you want to add a project_type_id column to the
table, that can't be NULL and has a foreign key to the project_types
table.
The database sees the project_type_id can't be NULL, so it inserts a
0 in the column for all the existing rows. The 0 does not exist in
the project_types table, so the foreign key constraint fails. This
means you can't add the foreign key.
I thought about adding a first "default" row to the project_types table and link all the projects to that type. This isn't a good solution, because the user has to change all the types from "default" to the correct type.
What is your solution for this problem?
I think the best way to solve this issue is:
Create a new migration in order to add project_type_id column to projects table:
php artisan make:migration add_project_type_column_to_projects_table --table=projects
public function up()
{
Schema::table('projects', function (Blueprint $table) {
$table->integer("project_type_id")->unsigned();
});
}
public function down()
{
Schema::table('projects', function (Blueprint $table) {
$table->dropColumn("project_type_id");
});
}
It's going to add 0 value as default in each column but no problem because it's not foreign key yet.
2.Define the relationship between Project and ProjectType models
//Your Project class
class Project extends Model
{
protected $fillable=["your columns..."];
public function type()
{
return $this->belongsTo(ProjectType::class);
}
}
//Your ProjectType class
class ProjectType extends Model
{
protected $fillable=["Your columns..."];
public function projects()
{
return $this->hasMany(Project::class);
}
}
Now you must go and set the correct values for project_type_id either manually or from your admin panel.
Now your column is ready to be set as foreign key, so go ahead and make a new migration to set the project_type_id as foreign key.
php artisan make:migration set_project_type_id_column_as_foreign_key --table=projects
public function up()
{
Schema::table('projects', function (Blueprint $table) {
$table->foreign("project_type_id")
->references("id")
->on("project_types")
->onDelete("cascade")//is up to you
->onUpdate("cascade");// is up to you
});
}
public function down()
{
Schema::table('projects', function (Blueprint $table) {
$table->dropForeign("projects_project_type_id_foreign"); //This one could be different
});
}
Now your table has a new (NOT NULLABLE) foreign key with correct values.
You should have a valid foreign key that exists in DB, for example: 1
Then Create a new migration :
$table->integer('project_type_id')->unsigned()->default(1);
$table->foreignkey('project_type_id')->references('id')->on('project_types');
Then user has to manually change every one of them or change them randomly (random valid ids) with foreach.
I think one workaround here would be to first add the new column without a foreign key reference. Then update the values of that column, and afterwards add the foreign key constraint. Something like this:
ALTER TABLE projects ADD COLUMN project_type_id INT;
-- now update with correct values
ALTER TABLE projects ADD CONSTRAINT fk_pt_id FOREIGN KEY (project_type_id)
REFERENCES yourTable(parent_id);
If you have to deploy to different environments, you could add a default value to the project_type_id (as #Ahmad Mobaraki suggested). Of course, you must have that value in the project_type table.
Then, create an Artisan command that fetches all rows and that updates the project_type_id to the right value, according to your business logic.
Finally, after running the migration, run that Artisan custom command.
At this point, you have a non-nullable foreign key with the right values.
I had the same issue (yes, I know, 2019) and solved that way. After deployment in other environments, just run the update script.
I use to create Artisan commands that are run manually after the automated deployment. Most of the time, automated deployments are just fine, but when there are some tasks like this, the best solution I have found is to have a custom Artisan command that handles that extra work.
Other solution I have explored in the past is to handle that updating in the same migration file, but that mixes things up. Database structure and data, and, on top of that, it assumes the database has data. With this approach, even when it is not fully automated, you have a clear separation between structure and data.

String as Primary Key in Laravel migration

I've had to change a table in my database so that the primary key isn't the standard increments.
Here's the migration,
public function up()
{
Schema::create('settings', function (Blueprint $table) {
$table->text('code', 30)->primary();
$table->timestamps();
$table->text('name');
$table->text('comment');
});
}
However, MySQL keeps returning with,
Syntax error or access violation: 1170 BLOB/TEXT column 'code' used in
key specification without a key length (SQL: alter table settings
add primary key settings_code_primary(code)
I've tried leaving the normal increments id in there and modifying the table in a different migration but the same thing happens.
Any ideas of what I'm doing wrong?
Laveral Version 5.4.23
Change it to string.
$table->string('code', 30)->primary();

why can not php artisan migrate laravel

when I tried php artisan migrate an error :
{"error":{"type":"Illuminate\\Database\\QueryException","message":"SQLSTATE[42S02]: Base table or view not found: 1051 Unknown table 'laravel.users' (SQL: drop table `users`)","file":"\/opt\/lampp\/htdocs\/laravel\/coba1\/latihan3\/vendor\/laravel\/framework\/src\/Illuminate\/Database\/Connection.php","line":625}}
I use the mysql database, please give solution
You are attempting to drop a table that does not exist. You are either not using the correct database (laravel) or you are doing this as part of a rollback or modification.
Remember that your migrations should include a function that makes changes (up) and a function that undoes those changes (down). Database: Migrations
public function up()
{
Schema::create('users', function (Blueprint $table) {
// columns
});
}
public function down()
{
Schema::drop('users');
}
If you are dropping a table that you are not sure exists you can
Schema::dropIfExists('users');
Check database name (it should be 'laravel' named or change yor config file to right database name) and check existing table users in your database.

Laravel 5.1 Unknown database type enum requested

While running php artisan migrate, I got the following error
[Doctrine\DBAL\DBALException]
Unknown database type enum requested, Doctrine\DBAL\Platforms\MySqlPlatform may not support it.
How to resolve this issue.
Code:
public function up() {
Schema::table('blogs', function (Blueprint $table) {
$table->string('wordpress_id')->nullable();
$table->string('google_blog_id')->nullable()->change();
});
}
It is a known issue as stated in Laravel 5.1 documentation.
Note: Renaming columns in a table with a enum column is not currently supported.
It happens when you have a enum column in your database table. Whether you are trying to rename another column, or change another column to nullable, this bug will appear. It's an issue with Doctrine\DBAL.
An easy fix for this is to just add this constructor method in your database migration file.
public function __construct()
{
DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
}
This will map all the ENUM columns to VARCHAR(), and the column will accept any string.
This worked for me on Laravel 5.1 and Laravel 5.3. I hope this bug can be fixed soon.
Credit to #Gmatkowski's answer at https://stackoverflow.com/a/32860409/1193201
The official Laravel 5.1 documentation states:
Note: Renaming columns in a table with a enum column is not currently supported.
It doesn't matter if you're trying to change another column, if the table contains a enum anywhere it won't work. It's a Doctrine DBAL issue.
As a workaround you could either drop the column and add a new one (column data will be lost):
public function up()
{
Schema::table('users', function(Blueprint $table)
{
$table->dropColumn('name');
});
Schema::table('users', function(Blueprint $table)
{
$table->text('username');
});
}
or use a DB statement:
public function up()
{
DB::statement('ALTER TABLE projects CHANGE slug url VARCHAR(200)');
}
public function down()
{
DB::statement('ALTER TABLE projects CHANGE url slug VARCHAR(200)');
}
Source: https://github.com/laravel/framework/issues/1186
I get rid of this problem by creating a new Migration Class and making my migrations extending from it. Maybe there are multiple ways to make it more "standard" but this is just a very simple case which works perfectly for our team.
use Doctrine\DBAL\Types\{StringType, Type};
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\{DB, Log};
/**
* Class ExtendedMigration
* Use it when the involved table(s) has enum type column(s)
*/
class ExtendedMigration extends Migration
{
/**
* ExtendedMigration constructor.
* Handle Laravel Issue related with modifying tables with enum columns
*/
public function __construct()
{
try {
Type::hasType('enum') ?: Type::addType('enum', StringType::class);
Type::hasType('timestamp') ?: Type::addType('timestamp', DateTimeType::class);
} catch (\Exception $exception) {
Log::info($exception->getMessage());
}
}
}
Then as explained before just extend your migration from it
class SampleMigration extends ExtendedMigration
{
public function up()
{
Schema::create('invitations', function (Blueprint $table) {
...
$table->enum('status', ['sent', 'consumed', 'expired'])->default('sent');
...
});
}
public function down()
{
Schema::dropIfExists('invitations');
}
}
You should not use enum at all. Even with laravel 5.8, problem is not resolved.
Thank's to everyone who reminded that
The official Laravel 5.1 documentation states:
Note: Renaming columns in a table with a enum column is not currently supported.
Plus you will have the same problem when adding available options into enum column declaration.
It brings me to a conclusion that You should use enum with care. or even You should not use enum at all.
I cannot vote up any answer that offer to replace enum with string. NO, you need to create a lookup table and replace enum with unsignedInteger as a foreign key.
It is a lot of work and you'll be upset doing it without previous unit-test coverage, but this is a right solution.
You may be even fired for doing this correctly, because it is taking too long, but, don't worry, you'll find a better job. :)
Here is an example of how difficult would it be adding available options into enum column declaration
say you have this:
Schema::create('blogs', function (Blueprint $table) {
$table->enum('type', [BlogType::KEY_PAYMENTS]);
$table->index(['type', 'created_at']);
...
and you need to make more types available
public function up(): void
{
Schema::table('blogs', function (Blueprint $table) {
$table->dropIndex(['type', 'created_at']);
$table->enum('type_tmp', [
BlogType::KEY_PAYMENTS,
BlogType::KEY_CATS,
BlogType::KEY_DOGS,
])->after('type');
});
DB::statement('update `blogs` as te set te.`type_tmp` = te.`type` ');
Schema::table('blogs', function (Blueprint $table) {
$table->dropColumn('type');
});
Schema::table('blogs', function (Blueprint $table) {
$table->enum('type', [
BlogType::KEY_PAYMENTS,
BlogType::KEY_CATS,
BlogType::KEY_DOGS,
])->after('type_tmp');
});
DB::statement('update `blogs` as te set te.`type` = te.`type_tmp` ');
Schema::table('blogs', function (Blueprint $table) {
$table->dropColumn('type_tmp');
$table->index(['type', 'created_at']);
});
}
Laravel: 5.8, 6, 7, 8
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Types\Type;
use Illuminate\Support\Facades\DB;
public function __construct()
{
if (! Type::hasType('enum')) {
Type::addType('enum', StringType::class);
}
// For point types
// DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('point', 'string');
DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
}
A real dirty solution, that gets the job done none the less would be to
update Doctrine/DBAL/Schema/MySqlSchemaManager.php
by ading these lines just above line 113
$this->_platform->registerDoctrineTypeMapping('enum', 'string');
$type = $this->_platform->getDoctrineTypeMapping($dbType);
Beware that updating vendor files directly is not advisable because in the event the vonder chooses to update the plugin, you changes could be overwritten
I think the easiest way to fix this issue is adding a mapping type to doctrine.yaml if applicable so that enum will be treated as string.
doctrine:
dbal:
#other configuration
mapping_types:
enum: string
You can either use the above suggestions or can add the below code to your migration file...
public function up()
{
DB::connection()->getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
Schema::table('<YOUR_TABLE>', function (Blueprint $table) {
//YOUR CHANGES HERE
}
}
If you ever run into the error below on a Doctrine with Laminas setup. Find the source of the enum column because my source code contains nothing like it.
Unknown database type enum requested
It was a table schema that phpMyAdmin created in the database that Doctrine wanted to match/sync with my schema definition. And it couldn’t find the enum column contained in the phpMyAdmin schema.
I solved it by moving the pma_ tables to a different database.

Categories