lucid icon indicating copy to clipboard operation
lucid copied to clipboard

Timestamp behavior on MariaDB is strange but MySQL is fine

Open guoyunhe opened this issue 2 years ago • 7 comments

Package version

18.3.0

Node.js and npm version

18.15.0 9.5.0

Sample Code (to reproduce the issue)

Make a very simple model with created_at and updated_at:

export default class Post extends BaseModel {
  /** ID */
  @column({ isPrimary: true })
  public id: number;

  @column()
  public title: string;

  /** Timestamp of creation */
  @column.dateTime({ autoCreate: true, autoUpdate: false })
  public createdAt: DateTime;

  /** Timestamp of modification */
  @column.dateTime({ autoCreate: true, autoUpdate: true })
  public updatedAt: DateTime;
}

And table migration:

export default class extends BaseSchema {
  protected tableName = 'posts';

  public async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id');
      table.string('title');
      table.timestamp('created_at', { useTz: true });
      table.timestamp('updated_at', { useTz: true });
    });
  }

  public async down() {
    this.schema.dropTable(this.tableName);
  }
}

In controller, I update an existing model:

post.title = 'foobar';
await post.save();

In MySQL, created_at stays the same while updated_at get updated. Work as expected.

But in MariaDB, both created_at and updated_at got updated, which is unexpected.

Table scheme in MariaDB:

+-------------+------------------+------+-----+---------------------+-------------------------------+
| Field       | Type             | Null | Key | Default             | Extra                         |
+-------------+------------------+------+-----+---------------------+-------------------------------+
| id          | int(10) unsigned | NO   | PRI | NULL                | auto_increment                |
| title       | varchar(255)     | YES  |     | NULL                |                               |
| created_at  | timestamp        | NO   |     | current_timestamp() | on update current_timestamp() |
| updated_at  | timestamp        | NO   |     | 0000-00-00 00:00:00 |                               |
+-------------+------------------+------+-----+---------------------+-------------------------------+

created_at is clearly wrong.

I checked MariaDB's doc and it seems by design that MariaDB will make first not null timestamp column auto-update:

CREATE TABLE t3 (id INT, ts1 TIMESTAMP, ts2 TIMESTAMP);

INSERT INTO t3(id)  VALUES (1),(2);

SELECT * FROM t3;
+------+---------------------+---------------------+
| id   | ts1                 | ts2                 |
+------+---------------------+---------------------+
|    1 | 2013-07-22 15:35:07 | 0000-00-00 00:00:00 |
|    2 | 2013-07-22 15:35:07 | 0000-00-00 00:00:00 |
+------+---------------------+---------------------+

DESC t3;
+-------+-----------+------+-----+---------------------+-----------------------------+
| Field | Type      | Null | Key | Default             | Extra                       |
+-------+-----------+------+-----+---------------------+-----------------------------+
| id    | int(11)   | YES  |     | NULL                |                             |
| ts1   | timestamp | NO   |     | CURRENT_TIMESTAMP   | on update CURRENT_TIMESTAMP |
| ts2   | timestamp | NO   |     | 0000-00-00 00:00:00 |                             |
+-------+-----------+------+-----+---------------------+-----------------------------+

Well, in MySQL doc, you have to manually specify default and auto update... (if i understand it correctly)

In short, MySQL and MariaDB treat your first TIMESTAMP NOT NULL column differently:

# Your SQL
created_at TIMESTAMP NOT NULL;
updated_at TIMESTAMP NOT NULL;

# MySQL creates (Good)
+-------+-----------+------+-----+---------------------+-----------------------------+
| Field | Type      | Null | Key | Default             | Extra                       |
+-------+-----------+------+-----+---------------------+-----------------------------+
| ts1   | timestamp | NO   |     | 0000-00-00 00:00:00 |                             |
| ts2   | timestamp | NO   |     | 0000-00-00 00:00:00 |                             |
+-------+-----------+------+-----+---------------------+-----------------------------+

# MariaDB creates (Bad)
+-------+-----------+------+-----+---------------------+-----------------------------+
| Field | Type      | Null | Key | Default             | Extra                       |
+-------+-----------+------+-----+---------------------+-----------------------------+
| ts1   | timestamp | NO   |     | CURRENT_TIMESTAMP   | on update CURRENT_TIMESTAMP |
| ts2   | timestamp | NO   |     | 0000-00-00 00:00:00 |                             |
+-------+-----------+------+-----+---------------------+-----------------------------+

A workaround can be, force the column to be nullable:

table.timestamp('created_at', { useTz: true }).nullable();
table.timestamp('updated_at', { useTz: true }).nullable();

If you don't like nullable, you can set a raw default:

table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(this.raw('CURRENT_TIMESTAMP'));
table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(this.raw('CURRENT_TIMESTAMP'));

guoyunhe avatar Apr 02 '23 14:04 guoyunhe