Windows installer: payload hash-locking, SBOM, client-IP handling, operator docs
This commit is contained in:
@@ -98,6 +98,31 @@ def preflight():
|
||||
elif version:
|
||||
ok('MySQL 5.7+/8.0 - no extra index flags needed')
|
||||
|
||||
# The database's own default charset. Tables are forced to utf8mb4 by the
|
||||
# compiler hook (shopdb/utils/mysql_charset.py), so a latin1 default does not
|
||||
# break the schema - but anything created OUTSIDE that path inherits it, and
|
||||
# a database created without an explicit CHARSET is the usual cause. Cheap to
|
||||
# check here, invisible until characters come back mangled.
|
||||
if version:
|
||||
try:
|
||||
row = db.session.execute(text(
|
||||
'SELECT DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA '
|
||||
'WHERE SCHEMA_NAME = DATABASE()')).fetchone()
|
||||
charset = row[0] if row else None
|
||||
if charset is None:
|
||||
warn('Could not read the database default charset',
|
||||
'Not fatal - tables are forced to utf8mb4 regardless.')
|
||||
elif str(charset).lower() == 'utf8mb4':
|
||||
ok('Database default charset is utf8mb4')
|
||||
else:
|
||||
warn(f'Database default charset is {charset}, not utf8mb4',
|
||||
'Tables this application creates are forced to utf8mb4, so the '
|
||||
'schema is correct. Fix the default so anything created outside '
|
||||
'the migrations matches: ALTER DATABASE <name> CHARACTER SET '
|
||||
'utf8mb4 COLLATE utf8mb4_unicode_ci;')
|
||||
except Exception as exc:
|
||||
warn('Could not read the database default charset', f'({exc})')
|
||||
|
||||
click.echo('')
|
||||
if failures:
|
||||
click.echo(click.style(f'{len(failures)} blocker(s). Fix them before installing.', fg='red', bold=True))
|
||||
|
||||
@@ -34,6 +34,14 @@ from typing import Iterable
|
||||
from alembic import context
|
||||
from sqlalchemy import MetaData, pool
|
||||
|
||||
# Registers the compiler hook that forces utf8mb4 + DYNAMIC on MySQL. Imported
|
||||
# for the side effect. It used to live inline in migrations/env.py, so it applied
|
||||
# to the CORE chain only: a plugin's baseline tables were created at the server's
|
||||
# default charset while core's were utf8mb4, on the same database. On a server
|
||||
# defaulting to latin1 that difference is invisible until a join between the two
|
||||
# stops using an index, or a character comes back mangled.
|
||||
import shopdb.utils.mysql_charset # noqa: F401,E402
|
||||
|
||||
logger = logging.getLogger('alembic.env.plugin')
|
||||
|
||||
# Explicit table-ownership map. Adding tables to a plugin requires updating
|
||||
|
||||
40
shopdb/utils/mysql_charset.py
Normal file
40
shopdb/utils/mysql_charset.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""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 West Jefferson 5.6
|
||||
server 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
|
||||
Reference in New Issue
Block a user