4

I created a table in Laravel 6.6 with the following definition.

public function up()
{
    Schema::create('quarters', function (Blueprint $table) {
        $table->integer('quarter_id')->unsigned();
        $table->integer('year')->unsigned();
        $table->integer('quarter_num')->unsigned();
        $table->timestamp('valid_from');
        $table->timestamp('valid_to'); // <------ error on this line
        $table->string('description')->nullable();
        $table->timestamps();
        $table->primary('quarter_id');
    });
} 

When I run the migration command I get the following error.

Illuminate\Database\QueryException : SQLSTATE[42000]: Syntax error or access violation: 1067 Invalid default value for 'valid_to' (SQL: create table quarters (quarter_id int unsigned not null, year int unsigned not null, quarter_num int unsigned not null, valid_from timestamp not null, valid_to timestamp not null, description varchar(255) null, created_at timestamp null, updated_at timestamp null) default character set utf8mb4 collate 'utf8mb4_unicode_ci')

here is the SQL generated by Eloquent:

CREATE TABLE `quarters`(
    `quarter_id` INT UNSIGNED NOT NULL,
    `year` INT UNSIGNED NOT NULL,
    `quarter_num` INT UNSIGNED NOT NULL,
    `valid_from` TIMESTAMP NOT NULL,
    `valid_to` TIMESTAMP NOT NULL,
    `description` VARCHAR(255) NULL,
    `created_at` TIMESTAMP NULL,
    `updated_at` TIMESTAMP NULL
) DEFAULT CHARACTER SET utf8mb4 COLLATE 'utf8mb4_unicode_ci'

The strange thing is that if I comment out the valid_to line then it creates the table with no error. But the definition of valid_to is 100% similar to valid_from, and it does not throw that error for the valid_from column. Actually it seems the DB does not allow for twotimestamp columns!

As requested in the comments I ran the php artisan migrate --pretend and here is the result:

C:\xampp\htdocs\voiceit> php artisan migrate --pretend
CreateQuartersTable: create table `quarters` (`quarter_id` int unsigned not null, `year` int unsigned not null, `quarter_num` int unsigned not null, `valid_from` timestamp not null, `valid_to` timestamp not null, `description` varchar(255) null, `created_at` timestamp null, `updated_at` timestamp null) default character set utf8mb4 collate 'utf8mb4_unicode_ci'
CreateQuartersTable: alter table `quarters` add primary key `quarters_quarter_id_primary`(`quarter_id`)
CreatePeopleDatasTable: create table `people_datas` (`mt_id` bigint unsigned not null, `valid_for` int unsigned not null, `local_personal_id` bigint unsigned not null, `first_name` varchar(255) not null, `last_name` varchar(255) not null, `date_of_birth` date null, `date_of_join` date null, `gender` varchar(1) not null, `location_type` varchar(1) not null, `created_at` timestamp null, `updated_at` timestamp null, `deleted_at` timestamp null) default character set utf8mb4 collate 'utf8mb4_unicode_ci'
CreatePeopleDatasTable: alter table `people_datas` add primary key `people_datas_mt_id_valid_for_primary`(`mt_id`, `valid_for`)
CreatePeopleDatasTable: alter table `people_datas` add constraint `people_datas_valid_for_foreign` foreign key (`valid_for`) references `quarters` (`quarter_id`)
CreatePeopleDatasTable: alter table `people_datas` add constraint `people_datas_gender_foreign` foreign key (`gender`) references `genders` (`id`)
CreatePeopleDatasTable: alter table `people_datas` add constraint `people_datas_location_type_foreign` foreign key (`location_type`) references `location_types` (`id`)
CreatePeopleDatasTable: alter table `people_datas` add index `people_datas_last_name_index`(`last_name`)
MJBZA
  • 4,796
  • 8
  • 48
  • 97
  • Need to make them nullable: ` $table->timestamp('valide_to')->nullable();` – Eric Day Dec 13 '19 at 15:50
  • But I don't want them nullable. I need value from user. Is it some limitations from the DB? Also if this is the case why it accepts not null for `valid_from`? – MJBZA Dec 13 '19 at 15:51
  • This is actually a pretty weird scenario. A nullableTimestamp seems to somehow solve the issue, but it's fighting symptoms rather that finding a solution to the real problem. Could you run `php artisan migrate --pretend` and add the output to the question? It looks like Laravel might somehow create faulty SQL here, since in theory it should add a CURRENT_TIMESTAMP as default for SQL (see https://github.com/laravel/framework/blob/5b1b3675748649da19c9b6308d1ade25f41eabd5/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php#L693 ) – Stratadox Dec 13 '19 at 16:01
  • @Stratadox I added. Also I run the SQL statement in the PhpMyAdmin and then I received the same error: `MySQL said: #1067 - Invalid default value for 'valid_to'`. It seems it is a wrong SQL or a limitation from the DB. – MJBZA Dec 13 '19 at 16:12
  • Which Mysql version are you using? It looks like the explicit_defaults_for_timestamp setting might be causing this problem: https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_explicit_defaults_for_timestamp – Stratadox Dec 13 '19 at 16:17
  • More info on this page: https://dev.mysql.com/doc/refman/8.0/en/timestamp-initialization.html – Stratadox Dec 13 '19 at 16:19
  • @Stratadox Here you go: `Server version: 10.4.6-MariaDB mariadb.org binary distribution` – MJBZA Dec 13 '19 at 16:21
  • I solved my issue by changing the type of the column from `timestamp` to `dateTime` so changing the table definition to `$table->dateTime('valid_from'); $table->dateTime('valid_to');` solved my issue as I need a date and a time, but still I am interested to know how can we have multiple not nullable timestamp columns. – MJBZA Dec 13 '19 at 16:25

3 Answers3

5

I solved my issue by changing the type of the column from timestamp to dateTime. So changing the table definition as follow solved my issue as I need a date and a time:

Schema::create('quarters', function (Blueprint $table) {
             $table->integer('quarter_id')->unsigned();
             $table->integer('year')->unsigned();
             $table->integer('quarter_num')->unsigned();
             $table->dateTime('valid_from');
             $table->dateTime('valid_to');            
             $table->string('description')->nullable();
             $table->timestamps();
             $table->primary('quarter_id');
        });

However, I am still interested to know how can we have multiple not null timestamp columns in a table.

MJBZA
  • 4,796
  • 8
  • 48
  • 97
4

The default behaviour for default values of timestamps in MySql and/or MariaDB is different for the first timestamp declaration than for subsequent timestamps.

MariaDB has special behavior for the first column that uses the TIMESTAMP data type in a specific table. For the first column that uses the TIMESTAMP data type in a specific table, MariaDB automatically assigns the following properties to the column:

DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

This means that if the column is not explicitly assigned a value in an INSERT or UPDATE query, then MariaDB will automatically initialize the column's value with the current date and time.

This automatic initialization for INSERT and UPDATE queries can also be explicitly enabled for a column that uses the TIMESTAMP data type by specifying the DEFAULT CURRENT_TIMESTAMP and ON UPDATE CURRENT_TIMESTAMP clauses for the column. In these clauses, any synonym of CURRENT_TIMESTAMP is accepted, including CURRENT_TIMESTAMP(), NOW(), LOCALTIME, LOCALTIME(), LOCALTIMESTAMP, and LOCALTIMESTAMP().

https://mariadb.com/kb/en/library/timestamp/

Ergo, the first timestamp (valid_from) gets the automatic default value of CURRENT_TIMESTAMP, which is an acceptable default value for non-nullable timestamp fields. The second field gets a different automatic default value, which appears to be unacceptable to the database.

A solution to the immediate problem might be to follow MariaDB's suggestion and use an explicit CURRENT_TIMESTAMP as default for the second timestamp (or both, while at it). In Laravel terms, this is probably something like $table->timestamp('valid_to')->useCurrent();.

Important to notice in this regard, is that the solution you seem to have chosen in the end (using datetime over timestamp) is probably a more apt solution to the problem: timestamps are somewhat weird data types that are mainly used as meta-data only. Especially in Mysql and MariaDB, they inherently suffer from the "Year 2038 problem", which isn't a problem for created_at or updated_at fields for another 18 years or so, but might be problematic when the valid_to can be some years in the future. And that's only in addition to the unintuitive automatic default values...

Stratadox
  • 1,291
  • 8
  • 21
0

You can solve it add or modify the server variable "explicit-defaults-for-timestamp"... You need set it to ON

https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_explicit_defaults_for_timestamp

  • 1
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/32615069) – Rohit Gupta Sep 06 '22 at 14:47
  • Maybe be a little more specific , ie. "modify the MySQL server variable EXPLICIT_DEFAULTS" – nettie Sep 08 '22 at 18:42