Repository: privacyidea/privacyidea

Issue:
MySQL DateTime must be told explicitely to save fractions of seconds
Since 9380eaee the audit log contains duration times of the request. Unfortunately MySQL by default doesn't save fractions in it's DateTime type. It has to be enabled explicitly.
With a compiled statement for SQLAlchemy the fractions for the DataTime object can be enabled only for MySQL.
The simple version would change all DataTime columns in all tables. This could be narrowed down to only use this for the audit table.
We need to check how other databases behave:
- [ ] Oracle
- [ ] Postgres



Discussion:

Resolving patch:
diff --git a/migrations/versions/3ba618f6b820_.py b/migrations/versions/3ba618f6b820_.py
new file mode 100644
--- /dev/null
+++ b/migrations/versions/3ba618f6b820_.py
@@ -0,0 +1,45 @@
+"""v3.6 add fraction to MySQL DateTime type in audit table
+
+Revision ID: 3ba618f6b820
+Revises: 888b56ed5dcb
+Create Date: 2021-05-05 11:27:13.705851
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '3ba618f6b820'
+down_revision = '888b56ed5dcb'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import mysql
+
+
+def upgrade():
+    try:
+        if op._proxy.migration_context.dialect.name == 'mysql':
+            with op.batch_alter_table('pidea_audit') as batch_op:
+                batch_op.alter_column('date', type_=mysql.DATETIME(fsp=6),
+                                      existing_type=mysql.DATETIME)
+                batch_op.alter_column('startdate', type_=mysql.DATETIME(fsp=6),
+                                      existing_type=mysql.DATETIME)
+                batch_op.alter_column('duration', type_=mysql.DATETIME(fsp=6),
+                                      existing_type=mysql.DATETIME)
+    except Exception as exx:
+        print("Could not add fraction to MySQL DateTime Type in audit table.")
+        print(exx)
+
+
+def downgrade():
+    try:
+        if op._proxy.migration_context.dialect.name == 'mysql':
+            with op.batch_alter_table('pidea_audit') as batch_op:
+                batch_op.alter_column('date', type_=mysql.DATETIME,
+                                      existing_type=mysql.DATETIME(fsp=6))
+                batch_op.alter_column('startdate', type_=mysql.DATETIME,
+                                      existing_type=mysql.DATETIME(fsp=6))
+                batch_op.alter_column('duration', type_=mysql.DATETIME,
+                                      existing_type=mysql.DATETIME(fsp=6))
+    except Exception as exx:
+        print("Could not remove fraction from MySQL DateTime Type in audit table.")
+        print(exx)
diff --git a/privacyidea/models.py b/privacyidea/models.py
--- a/privacyidea/models.py
+++ b/privacyidea/models.py
@@ -52,6 +52,8 @@
                                     get_rand_digit_str)
 from sqlalchemy import and_
 from sqlalchemy.schema import Sequence
+from sqlalchemy.ext.compiler import compiles
+from sqlalchemy.sql.expression import ColumnElement
 from .lib.log import log_with
 from privacyidea.lib.utils import (is_true, convert_column_to_unicode,
                                    hexlify_and_unicode)
@@ -72,6 +74,12 @@
 db = SQLAlchemy()
 
 
+# Add fractions to the MySQL DataTime column type
+@compiles(db.DateTime, "mysql")
+def compile_datetime_mysql(type_, compiler, **kw):  # pragma: no cover
+    return "DATETIME(6)"
+
+
 class MethodsMixin(object):
     """
     This class mixes in some common Class table functions like
@@ -2654,7 +2662,7 @@ class Audit(MethodsMixin, db.Model):
     id = db.Column(db.Integer, Sequence("audit_seq"), primary_key=True)
     date = db.Column(db.DateTime)
     startdate = db.Column(db.DateTime)
-    duration = db.Column(db.Interval)
+    duration = db.Column(db.Interval(second_precision=6))
     signature = db.Column(db.Unicode(audit_column_length.get("signature")))
     action = db.Column(db.Unicode(audit_column_length.get("action")))
     success = db.Column(db.Integer)
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -43,7 +43,7 @@ def get_file_list(file_path):
                     "Flask-SQLAlchemy>=2.0",
                     "Flask-Versioned>=0.9.4",
                     "future>=0.18.2;python_version<'3.0'",
-                    "google-auth>=1.24.0",
+                    "google-auth>=1.23.0",
                     "huey[redis]>=1.11.0",
                     "ldap3>=2.6",
                     "netaddr>=0.7.12",


Test patch: