Files
shopdb-flask/shopdb/utils/mysql_charset.py

41 lines
1.8 KiB
Python

"""Force every table created on MySQL to utf8mb4 + DYNAMIC row format.
Importing this module installs a SQLAlchemy compiler hook. It is a side effect on
purpose: the hook has to be registered before any CreateTable is compiled, and
every place that creates tables needs it.
WHY. Without it a CREATE TABLE inherits the SERVER's default charset. A MySQL box
defaulting to latin1 - common on older installs, and the 5.6 server this was
ported from is one - silently builds a latin1 schema that drifts from the utf8mb4
production target. Nothing fails at create time; it surfaces later as mangled
characters, or as a join between a utf8mb4 and a latin1 column that cannot use an
index.
DYNAMIC row format is the other half: it keeps utf8mb4 indexes under the 767-byte
prefix limit on pre-5.7 InnoDB, which is what error 1071 ("key too long") is.
This lived inline in migrations/env.py, so it covered the CORE chain only. Plugin
chains (ADR-008) run through shopdb/plugins/alembic_template.py, which never
imported it - so a plugin's baseline tables were created at the server default
while core's were utf8mb4, on the same database. Both import this now.
Scoped to the mysql dialect, so the SQLite test database is untouched.
"""
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.schema import CreateTable
TABLE_SUFFIX = (
" ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
" COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC"
)
@compiles(CreateTable, "mysql")
def _mysql_create_table_utf8mb4(element, compiler, **kw):
sql = compiler.visit_create_table(element, **kw)
# An explicit CHARSET in the model's __table_args__ wins - this is a default,
# not an override.
if "CHARSET" not in sql.upper():
sql = sql.rstrip().rstrip(";") + TABLE_SUFFIX
return sql