简体   繁体   中英

Prevent MySQL to truncate values in Flask-SQLAlchemy

I have a Flask app that connects to a database using Flask-SQLAlchemy. When i want to insert a value to a table that is too long or invalid(ish), i usually get a warning that the value is truncated, and MySQL happily insert the valid part of the value. This results in various problems later.

Checking the sql_mode variable during my connection yields the following modes:

  • STRICT_TRANS_TABLES
  • ERROR_FOR_DIVISION_BY_ZERO
  • NO_AUTO_CREATE_USER
  • NO_ENGINE_SUBSTITUTION

To achieve my goal, i need to add STRICT_ALL_TABLES to this mix, which i can easily do with this (pretty dump) snippet ( db is the SQLAlchemy instance in my app):

res = list(db.engine.execute('SHOW VARIABLES LIKE \'sql_mode\''))
modes = res[0][1].split(',')
db.engine.execute(f'SET sql_mode = \'{",".join(modes)}\'')

# I tried and failed using the following for some reason
db.engine.execute('SET sql_mode = ?', '.'.join(modes)

After this, when i update a model with an unfitting value, i get exactly what i want; an exception instead of a warning:

sqlalchemy.exc.DataError: (pymysql.err.DataError) (1265, "Data truncated for column 'value' at row 1")

Question is, how can i tell SQLAlchemy to do this every time when i connect to a MySQL instance?

With some help from @gord-thompson's documentation link in the comments, i devised the following solution:

from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import event
from sqlalchemy.dialects.mysql.base import MySQLDialect


def mysql_on_connect(connection, conn_record):  # pylint: disable=unused-variable
    """Turn on strict mode for all tables
    """

    cursor = connection.cursor()

    # Get the current SQL Mode
    cursor.execute('SHOW VARIABLES LIKE \'sql_mode\'')
    res = list(cursor)
    modes = set(res[0][1].split(','))

    # Add STRICT_ALL_TABLES to the list of modes
    modes.add('STRICT_ALL_TABLES')
    new_modes = ','.join(modes)
    cursor.execute('SET sql_mode = %s', new_modes)

    cursor.close()


class StrictSQLAlchemy(SQLAlchemy):
    def init_app(self, app, *args, **kwargs):
        super().init_app(app, *args, **kwargs)

        with app.app_context():
            if isinstance(self.engine.dialect, MySQLDialect):
                event.listens_for(self.engine, 'connect')(mysql_on_connect)


db = StrictSQLAlchemy()


class User(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
    username = db.Column(db.String(length=20), unique=True)


app = Flask()
db.init_app(app)

user = User(username='a_very_long_username_that_doesnt_fit_in_20_characters')
db.session.add(user)
db.session.commit()

And now, instead of a warning and a truncated value, I get a proper exception:

sqlalchemy.exc.DataError: (pymysql.err.DataError) (1406, "Data too long for column 'username' at row 1")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM