question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

sqlalchemy does not emit server_onupdate ON UPDATE clause with MySQL

See original GitHub issue

Migrated issue, originally created by Charles-Axel Dein (@charlax)

This seems similar to #3155 and #2631.

I have this simple script to reproduce the problem:

from sqlalchemy import create_engine
from sqlalchemy import func
from sqlalchemy.dialects.mysql import DATETIME
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer, DateTime

Base = declarative_base()


class Timestamp(Base):

    __tablename__ = 'timestamps'

    id = Column(Integer(), primary_key=True)
    created_at = Column(DateTime(), nullable=False,
                        server_default=func.current_timestamp())
    updated_at = Column(DateTime(), nullable=False,
                        server_default=func.current_timestamp(),
                        server_onupdate=func.current_timestamp())


if __name__ == '__main__':
    engine = create_engine('mysql://root:root@localhost/test', echo=True)
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)

The SQL that sqlalchemy generates does not have the ON UPDATE clause:

CREATE TABLE timestamps (
	id INTEGER NOT NULL AUTO_INCREMENT,
	created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	PRIMARY KEY (id)
)

A SHOW CREATE TABLE query confirms that fact:

CREATE TABLE `timestamps` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

I’m not 100% sure I’m not causing the problem or have fully understood the two similar tickets. I’ll read about it and follow up on this thread if I find something.

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Reactions:2
  • Comments:8

github_iconTop GitHub Comments

4reactions
sqlalchemy-botcommented, Nov 27, 2018

Charles-Axel Dein (@charlax) wrote:

Leaving this for future reference, and people who might stumble upon this: http://jasonbos.co/two-timestamp-columns-in-mysql/

So basically getting a model with database-level created_at and updated_at behavior requires some shenanigans that you recommend against in #3155. Sadly, wasn’t able to find another way to achieve this.

from sqlalchemy import create_engine
from sqlalchemy import func, text
from sqlalchemy.dialects.mysql import TIMESTAMP
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer, DateTime
from sqlalchemy.orm import sessionmaker

Base = declarative_base()


class Timestamp(Base):

    __tablename__ = 'timestamps'

    id = Column(Integer(), primary_key=True)
    created_at = Column(TIMESTAMP(), nullable=False,
                        server_default=text('0'))
    updated_at = Column(TIMESTAMP(), nullable=False,
                        server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
                        )


if __name__ == '__main__':
    engine = create_engine('mysql://root:root@localhost/test', echo=True)
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)

    Session = sessionmaker(bind=engine)
    session = Session()
    print session.execute('SHOW CREATE TABLE timestamps').fetchone()[1]

Which outputs:

2015-06-09 14:04:18,459 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'
2015-06-09 14:04:18,459 INFO sqlalchemy.engine.base.Engine ()
2015-06-09 14:04:18,460 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
2015-06-09 14:04:18,460 INFO sqlalchemy.engine.base.Engine ()
2015-06-09 14:04:18,461 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8' and `Collation` = 'utf8_bin'
2015-06-09 14:04:18,461 INFO sqlalchemy.engine.base.Engine ()
2015-06-09 14:04:18,462 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1
2015-06-09 14:04:18,462 INFO sqlalchemy.engine.base.Engine ()
2015-06-09 14:04:18,462 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1
2015-06-09 14:04:18,463 INFO sqlalchemy.engine.base.Engine ()
2015-06-09 14:04:18,463 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8) COLLATE utf8_bin AS anon_1
2015-06-09 14:04:18,463 INFO sqlalchemy.engine.base.Engine ()
2015-06-09 14:04:18,464 INFO sqlalchemy.engine.base.Engine DESCRIBE `timestamps`
2015-06-09 14:04:18,464 INFO sqlalchemy.engine.base.Engine ()
2015-06-09 14:04:18,467 INFO sqlalchemy.engine.base.Engine
DROP TABLE timestamps
2015-06-09 14:04:18,467 INFO sqlalchemy.engine.base.Engine ()
2015-06-09 14:04:18,468 INFO sqlalchemy.engine.base.Engine COMMIT
2015-06-09 14:04:18,469 INFO sqlalchemy.engine.base.Engine DESCRIBE `timestamps`
2015-06-09 14:04:18,469 INFO sqlalchemy.engine.base.Engine ()
2015-06-09 14:04:18,470 INFO sqlalchemy.engine.base.Engine ROLLBACK
2015-06-09 14:04:18,470 INFO sqlalchemy.engine.base.Engine
CREATE TABLE timestamps (
	id INTEGER NOT NULL AUTO_INCREMENT,
	created_at TIMESTAMP NOT NULL DEFAULT 0,
	updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
	PRIMARY KEY (id)
)


2015-06-09 14:04:18,470 INFO sqlalchemy.engine.base.Engine ()
2015-06-09 14:04:18,479 INFO sqlalchemy.engine.base.Engine COMMIT
2015-06-09 14:04:18,479 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2015-06-09 14:04:18,480 INFO sqlalchemy.engine.base.Engine SHOW CREATE TABLE timestamps
2015-06-09 14:04:18,480 INFO sqlalchemy.engine.base.Engine ()
CREATE TABLE `timestamps` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

And then, you need to pass a value of NULL on create:

mysql> insert into timestamps (id) value (1);
Query OK, 1 row affected (0.00 sec)

mysql> select * from timestamps;
+----+---------------------+---------------------+
| id | created_at          | updated_at          |
+----+---------------------+---------------------+
|  1 | 0000-00-00 00:00:00 | 2015-06-09 14:10:46 |
+----+---------------------+---------------------+
1 row in set (0.00 sec)

mysql> insert into timestamps (created_at, id) value (NULL, 2);
Query OK, 1 row affected (0.00 sec)

mysql> select * from timestamps;
+----+---------------------+---------------------+
| id | created_at          | updated_at          |
+----+---------------------+---------------------+
|  1 | 0000-00-00 00:00:00 | 2015-06-09 14:10:46 |
|  2 | 2015-06-09 14:11:14 | 2015-06-09 14:11:14 |
+----+---------------------+---------------------+
2 rows in set (0.00 sec)
1reaction
sqlalchemy-botcommented, Nov 27, 2018

Charles-Axel Dein (@charlax) wrote:

Probably the last update here, left for posterity: was getting too complicated, so ended up defining all of this on the app side instead of database-side.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Column INSERT/UPDATE Defaults
The Column.server_onupdate directive does not currently produce MySQL's “ON UPDATE CURRENT_TIMESTAMP()” clause. See Rendering ON UPDATE CURRENT TIMESTAMP ...
Read more >
How to set DEFAULT ON UPDATE CURRENT_TIMESTAMP ...
If all you want is DEFAULT CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP in MySQL, then just setting the TIMESTAMP column in sqlalchemy to ...
Read more >
sqlalchemy Changelog - pyup.io
joined-inheritance subclass, updating only local table columns, where the "fetch" synchronization strategy would not render the correct RETURNING clause for ...
Read more >
Column Insert/Update Defaults
When an update statement executes and no value is passed for last_updated ... specific to MySQL, when an UPDATE statement is emitted for...
Read more >
SQLAlchemy - Quick Guide - Tutorialspoint
In MySQL, providing updates to column values is based on that of other column values. Following statement's result − UPDATE table1 SET x...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found