Laravel & PostGres - Migration down() will not drop table (TimeScaleDB Extension) - laravel

Here is what create_facts_table looks like:
class CreateFactsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::connection('pgsql')->create('facts', function (Blueprint $table) {
$table->bigInteger('entry_id');
$table->string('tag');
$table->timestampTz('time');
$table->double('sensor_value');
$table->index(['entry_id', 'tag', 'time']);
$table->unique(['entry_id', 'tag', 'time']);
});
DB::connection('pgsql')->statement('SELECT create_hypertable(\'facts\', \'time\');');
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
// Tried both, neither would delete the table
Schema::connection('pgsql')->dropIfExists('facts');
//DB::connection('pgsql')->statement('DROP TABLE facts;');
}
}
I don't get any error from down(). I am able to login as the DB user specified in my .env file and run DROP TABLE facts.
When I run php artisan migration:fresh up() fails to create the table and throws a duplicate table error:
Duplicate table: 7 ERROR: relation "facts" already exists
After manually deleting the table, I can then run php artisan migration:fresh. I must specify connection('pgsql') as I'm using multiple databases. Semi-unrelated, but I'm using TimeScaleDB extension (hence create_hypertable())

I dug a bit deeper on this recently. I hadn't directly mentioned I was using the TimeScaleDB extension ('SELECT create_hypertable(\'facts\', \'time\');').
I updated the title of the question incase someone lands here from Google in the future.
Here is what I have learned:
When php artisan migration:fresh is run, it attempts to drop all tables in batch and does not use the down() method. TimeScaleDB hypertables cannot be deleted in batch.
The solution is to use php artisan migration:refresh instead which will run the defined drop() operations for each table.
Source: https://stackoverflow.com/a/69105447/5449796

Related

Laravel Many To Many (Polymorphic) issue

I've been trying to create a Many To Many (Polymorphic) system that saves the state of every update done by a specific user on specific Models (for instance: Company, Address, Language models) with a note field and the updated_by. The goal is to keep track of who updated the model and when, and the note field is a text field that states where in the system the model was updated.
Below is what I created, but I'm open to getting a different solution that, in the end, allows me to accomplish the goal described above.
I've created the model Update (php artisan make:model Update -m) with the migration:
/**
* Run the migrations.
*
* #access public
* #return void
* #since
*/
public function up()
{
Schema::create('updates', function (Blueprint $table) {
$table->id()->comment('The record ID');
$table->bigInteger('updatable_id')->unsigned()->comment('THe model record id');
$table->string('updatable_type')->comment('THe model name');
$table->string('note')->nullable()->comment('The record note');
$table->integer('updated_by')->unsigned()->nullable()->comment('The user ID which updated the record');
$table->timestamps();
$table->softDeletes();
});
}
On the model Update all the $fillable, $dates properties are standard, and the method of the Update model:
class Update extends Model
{
/**
* Method to morph the records
*
* #access public
*/
public function updatable()
{
return $this->morphTo();
}
}
After trying several ways on the different models, my difficulty is getting the relation because when I save to the updates table, it saves correctly. For instance, in the Company model: Company::where('id', 1)->with('updates')->get(); as in the model Company I have the method:
public function updates()
{
return $this->morphToMany(Update::class, 'updatable', 'updates')->withPivot(['note', 'updated_by'])->withTimestamps();
}
Most certainly, I'm doing something wrong because when I call Company::where('id', 1)->with('updates')->get(); it throws an SQL error "Not unique table/alias".
Thanks in advance for any help.
The problem I see here is using morphToMany instead of morphMany.
return $this->morphToMany(Update::class, 'updateable', 'updates'); will use the intermediate table (and alias) updates (3rd argument) instead of using the default table name updateables. Here it will clash with the table (model) updates, so it will produce the error you are receiving.
return $this->morphMany(Update::class, 'updateable'); will use the table updates and should work with your setup.
Do notice that morphMany does not work with collecting pivot fields (e.g. withPivot([..]), it's not an intermediate table. Only morphToMany does.

How to index table column of specific database with code in laravel

I want to write code to index the column of a specific database table column.
Iam trying in this way
DB::connection('mysql2')->raw("ALTER TABLE `consignments` DROP INDEX customer_reference");
What is correct method for this process
You need to use the Laravel migrations, see this documentation:
Laravel Migrations
Laravel Migrations/Index related
You can execute this command in the shell on the project path and edit the created file in the database/migration path and add the index for the column following the documentation
php artisan make:migration add_index_to_consignments_table
Or create manually a file in the database/migrations path with a name like this:
2021_07_29_022532_add_index_to_consignments_table.php and copy/paste this php content
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddIndexToConsignmentsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up(): void
{
Schema::table('consignments', function (Blueprint $table) {
//Uncomment one line depending on what you need
//$table->index('customer_reference'); //If you need to add an index
//$table->dropIndex('name_of_index_on_table'); //If you need to remove an index
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down(): void
{
Schema::table('consignments', function (Blueprint $table) {
//Uncomment one line depending on what you need
//$table->dropIndex('name_of_index_on_table'); //If you need to add an index
//$table->index('customer_reference'); //If you need to remove an index
});
}
}
After creating the file with any method you have to execute this command in the shell on the project path:
php artisan migrate

Laravel PHPUnit error mysql fulltext index

Just got an error while running PHPUnit in Laravel after add FULLTEXT index
Doctrine\DBAL\Driver\PDOException: SQLSTATE[HY000]: General error: 1 near "name": syntax error
After investigating, the error caused by migration that I've added
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddFulltextIndexToProductName extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
DB::statement('ALTER TABLE products ADD FULLTEXT fulltext_index(name)');
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
DB::statement('DROP INDEX `fulltext_index`');
}
}
If I remove that migration code, the test running gracefully
Has anyone experienced this problem?
Usually Laravel tests are using in memory sqlite database and this statement in your migration will not work.
You can check how to create sqlite full text index here: https://www.sqlitetutorial.net/sqlite-full-text-search/
Since Laravel doesn't support full text search out of the box I assume you have written custom function which probably also will not work in tests.
To overcome the issue you can:
use mysql for test (not recommended since it is slow)
use repositories pattern
skip the migration when in tests (if you are not
testing the search - you don't need it). You can check with
if(env('APP_ENV') === 'testing')
use laravel scout for search

Can we create a migration for the data, not just structure of the tables

I love laravel because of the ability to create migrations.
From what I understood, laravel gives us the capability to create a migration where we can repeat the same process without having to create tables and structures manually.
My Question:
Similarly,
1) If I want that my data (inputs to the table) is also stored in some way, so that whenever i change the data in the database, that can also be reverted back or the whole process can also be recreated.
2) If 1 is not possible then can we have a way to save the methods for the "initial" seeding of the database. (so when we "factory" reset the whole thing, it can also automatically populate the content of the database, not just the structure of the database)
Any references for the same please?
I hope I was able to make myself clear!
You're correct in assuming Laravel is incredible! So with respect to your first question.
1) If I want that my data (inputs to the table) are also stored in some way, so that whenever i change the data in the database, that can also be reverted back or the whole process can also be recreated.
If you you want to recreate the data you will need to create a table seeder. To do this, simply create a seeder and a factory with artisan.
php artisan make:seeder UsersTableSeeder
After making your seeder you can run it this with command:
composer dump-autoload && php artisan db:seed
If you wanted to create Controllers, Seeders and Factories at the same time when you make a model type this artisan command.
php artisan make:model User -fa
You can see more on the Laravel documentation for creating seeders and factories here.
Instead of messing with your migration files, I would create seeders. Here is a couple examples.
Exhibit 1 - Example of a an Article Factory (database/factories/ArticleFactory.php)
<?php
use Faker\Generator as Faker;
$factory->define(App\Article::class, function (Faker $faker) {
return [
'title' => $faker->text(50),
'slug' => $faker->unique()->slug,
'body' => $faker->text(200),
'user_id' => rand(1,10),
];
});
Exhibit 2 - Example of an Article Seeder (database/seeds/ArticleTableSeeder.php)
<?php
use Illuminate\Database\Seeder;
class ArticlesTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
factory(App\Article::class, 10)->create();
}
}
Exhibit 3 - Example of an Article Migration (database/migrations/2018_05_13_create_articles_table.php)
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateArticlesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('body');
$table->string('slug');
$table->integer('media_id')->nullable();
$table->integer('user_id')->nullable(); // Owner of Article
$table->timestamps();
$table->softDeletes();
$table->index('slug');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('articles');
}
}
Exhibit 4 - DatabaseTableSeeder.php
<?php
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
use Faker\Factory as Faker;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* #return void
*/
public function run()
{
// Disable all mass assignment restrictions
Model::unguard();
// Seeds the Articles
$this->call(ArticlesTableSeeder::class);
Then to do a complete factory reset, all you need to do is type the following artisan command:
php artisan migrate:db --fresh
php artisan db:seed
Answer 1) Yes you can manually automatically insert your data into your tables each time you run your migrations.
Answer 2) Question one is possible
Example:
public function up()
{
// Create the table
Schema::create('users', function($table){
$table->increments('id');
$table->string('email', 255);
$table->string('password', 64);
$table->boolean('verified');
$table->string('token', 255);
$table->timestamps();
});
// Insert some stuff
DB::table('users')->insert(
array(
'email' => 'name#domain.com',
'verified' => true
)
);
}
Although you can do data migrations by both using a migration file or a seeder file (as explained in the above answers), from experience I strongly recommend that you put such migration code inside a seeder file, not a migration file.
The reason being is that it's very difficult to run a single file migration. Migrations are designed to run all at once, or to run migrations in step by step sequence since the last migration was done, migrations are not designed to be cherry picked individually, see migration help:
php artisan migrate --help
Usage:
migrate [options]
Options:
--database[=DATABASE] The database connection to use.
--force Force the operation to run when in production.
--path[=PATH] The path of migrations files to be executed.
--pretend Dump the SQL queries that would be run.
--seed Indicates if the seed task should be re-run.
--step Force the migrations to be run so they can be rolled back individually.
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
--env[=ENV] The environment the command should run under
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Help:
Run the database migrations
you will notice that there is no option available to run a handpicked migration, something that you may want to do some day with your data migration (example: suppose you simply want to take data from one legacy table to another table, think of it as moving data from a transactional database to an analytics database using a nightly cron job or something).
This option is however available in a seeder:
php artisan db:seed --help
Usage:
db:seed [options]
Options:
--class[=CLASS] The class name of the root seeder [default:
which makes it much more flexible than a migration (not to mention that seeding data is all about data, which is more appropriate for your task)

How to populate database with pre-set data? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed last year.
Improve this question
I am trying to run finished web app but it gives me error which is almost certainly due to app trying to find out which language version to use. I've done succesful migration but all tables (country, language etc. ) are empty. How do I populate them with data the app is looking for?
You can create default items as part of your migrations.
public function up()
{
Schema::create('users', function(Blueprint $table) {
$table->increments('id');
$table->string('name');
});
$user = new App\User;
$user->name = 'Bob';
$user->save();
}
You can also build seeders.
There is a default DatabaseSeeder class included in a freshly installed Laravel app. It allows you to insert data to the database. Look for it in the database/seeds directory:
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
// $this->call(UsersTableSeeder::class);
}
}
If you want to add data to the countries table, write it like this:
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
DB::table('countries)->insert([
'code' => 'fra',
'name' => 'France',
]);
}
}
Then use the db:seed command to seed your database:
php artisan db:seed
That's it.
You can also create your own Seeders, for example a ProductionDatabaseSeeder or a DummyDataSeeder, auto run your seeders when you migrate a database or even use model factories to easily generate dummy data. See more in the laravel docs.
I decided to go with the approach of using the migration, so that the database will be filled with the data I need straight immediately after the rows themselves are created. However, instead of adding each record individually as in ceejayoz's answer, I found it more efficient to use mass assignment functions:
class CreateManufacturersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->string("slug")->unique();
$table->string("name");
$table->timestamps();
});
Manufacturer::firstOrCreate(
["slug" => "bob", "name" => "Bob"]
);
Manufacturer::firstOrCreate(
["slug" => "alice", "name" => "Alice"]
);
Manufacturer::firstOrCreate(
["slug" => "hashim", "name" => "Hashim"]
);
}
The firstOrCreate() method saves you from needing to create a new instance of the model as well as persisting every record that you just created.

Resources