From: Mike Bayer Date: Mon, 29 Jun 2020 18:35:22 +0000 (-0400) Subject: Docuemnt current workarounds for MySQL TIMESTAMP X-Git-Tag: rel_1_4_0b1~252 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a2c2dcaf082ecf97c7e06cd23adf5a614b5a4a2b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Docuemnt current workarounds for MySQL TIMESTAMP MySQL 8 no longer generates the DEFAULT or ON UPDATE clauses for TIMESTAMP by default, hence users will begin to hit this regularly. add warnings that this non-standard SQL is not accommodated by server_onupdate and docuemnt the workaround used in issues such as #4652. Fixes: #5427 Change-Id: Ie048dcc91c648dd0b80ed395208c1d665b6c968b --- diff --git a/doc/build/core/defaults.rst b/doc/build/core/defaults.rst index 3d6124b438..3884faa861 100644 --- a/doc/build/core/defaults.rst +++ b/doc/build/core/defaults.rst @@ -292,6 +292,8 @@ behaviors such as seen with TIMESTAMP columns on some platforms, as well as custom triggers that invoke upon INSERT or UPDATE to generate a new value, may be called out using :class:`.FetchedValue` as a marker:: + from sqlalchemy.schema import FetchedValue + t = Table('test', meta, Column('id', Integer, primary_key=True), Column('abc', TIMESTAMP, server_default=FetchedValue()), @@ -312,6 +314,13 @@ returned. For details on using :class:`.FetchedValue` with the ORM, see :ref:`orm_server_defaults`. +.. warning:: The :paramref:`_schema.Column.server_onupdate` directive + **does not** currently produce MySQL's + "ON UPDATE CURRENT_TIMESTAMP()" clause. See + :ref:`mysql_timestamp_onupdate` for background on how to produce + this clause. + + .. seealso:: :ref:`orm_server_defaults` diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index b34422e655..e64bd97cc6 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -670,10 +670,76 @@ with the ``unique=True`` setting present in the :attr:`_schema.Table.indexes` collection. +TIMESTAMP issues +----------------- + +.. _mysql_timestamp_onupdate: + +Rendering ON UPDATE CURRENT TIMESTAMP for MySQL's explicit_defaults_for_timestamp +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +MySQL has historically expanded the DDL for the :class:`_types.TIMESTAMP` +datatype into the phrase "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE +CURRENT_TIMESTAMP", which includes non-standard SQL that automatically updates +the column with the current timestamp when an UPDATE occurs, eliminating the +usual need to use a trigger in such a case where server-side update changes are +desired. + +MySQL 5.6 introduced a new flag `explicit_defaults_for_timestamp +`_ which disables the above behavior, +and in MySQL 8 this flag defaults to true, meaning in order to get a MySQL +"on update timestamp" without changing this flag, the above DDL must be +rendered explicitly. + +SQLAlchemy's MySQL dialect does not yet have an option to generate +MySQL's "ON UPDATE CURRENT_TIMESTAMP" clause, noting that this is not a general +purpose "ON UPDATE" as there is no such syntax in standard SQL. SQLAlchemy's +:paramref:`_schema.Column.server_onupdate` parameter is currently not related +to this special MySQL behavior. + +To generate this DDL, make use of the :paramref:`_schema.Column.server_default` +parameter and pass a textual clause that also includes the ON UPDATE clause:: + + from sqlalchemy import Table, MetaData, Column, Integer, String, TIMESTAMP + from sqlalchemy import text + + metadata = MetaData() + + mytable = Table( + "mytable", + metadata, + Column('id', Integer, primary_key=True), + Column('data', String(50)), + Column( + 'last_updated', + TIMESTAMP, + server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") + ) + ) + +Even though the :paramref:`_schema.Column.server_onupdate` feature does not +generate this DDL, it still may be desirable to signal to the ORM that this +updated value should be fetched. This syntax looks like the following:: + + from sqlalchemy.schema import FetchedValue + + class MyClass(Base): + __tablename__ = 'mytable' + + id = Column(Integer, primary_key=True) + data = Column(String(50)) + last_updated = Column( + TIMESTAMP, + server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"), + server_onupdate=FetchedValue() + ) + + .. _mysql_timestamp_null: TIMESTAMP Columns and NULL --------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MySQL historically enforces that a column which specifies the TIMESTAMP datatype implicitly includes a default value of diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py index 3df1c9f916..a3f67fc682 100644 --- a/lib/sqlalchemy/sql/schema.py +++ b/lib/sqlalchemy/sql/schema.py @@ -1259,6 +1259,12 @@ class Column(DialectKWArgs, SchemaItem, ColumnClause): implement any kind of generation function within the database, which instead must be specified separately. + + .. warning:: This directive **does not** currently produce MySQL's + "ON UPDATE CURRENT_TIMESTAMP()" clause. See + :ref:`mysql_timestamp_onupdate` for background on how to + produce this clause. + .. seealso:: :ref:`triggered_columns`