Files
Pivot/services/tabelnamen.py
2026-10-14 13:34:37 +02:00

149 lines
6.3 KiB
Python

"""
Tabelnamen met hoofdletters herstellen (Windows → Linux).
MySQL op Windows bewaart tabelnamen standaard in kleine letters (lower_case_table_names = 1) en maakt hoofdletters
niet uit. Op Linux (lower_case_table_names = 0) zijn 'toestellen' en 'Toestellen' twee verschillende tabellen.
Een database die van Windows komt, heeft dan enkel kleine letters, terwijl PIVOT 'Toestellen' zoekt (en een lege
nieuwe aanmaakt).
Bij het opstarten loopt PIVOT daarom de centrale database en de database van elke school na. Enkel op een server
waar hoofdletters tellen (lower_case_table_names = 0), en enkel voor de tabellen die PIVOT kent:
- alleen 'toestellen' bestaat → hernoemd naar 'Toestellen'
- beide bestaan, 'Toestellen' is leeg → de lege 'Toestellen' weg, 'toestellen' hernoemd naar 'Toestellen'
- beide bestaan, 'toestellen' is leeg → niets nodig ('Toestellen' wordt gebruikt), enkel gemeld
- beide bevatten gegevens → niets aangeraakt, gemeld in het logboek (handmatig samenvoegen)
Op Windows (of met lower_case_table_names = 1 of 2) doet dit niets.
"""
import glob
import os
import re
import sys
_RE = re.compile(r"CREATE\s+TABLE\s+IF\s+NOT\s+EXISTS\s+`?([A-Za-z_][A-Za-z0-9_]*)`?", re.I)
_namen = None
def verwachte_namen():
"""Alle tabelnamen met minstens één hoofdletter, zoals PIVOT ze aanmaakt (uit de SQL-bestanden en de code)."""
global _namen
if _namen is None:
basis = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
bestanden = glob.glob(os.path.join(basis, 'sql', '*.sql')) + glob.glob(os.path.join(basis, '*.py')) \
+ glob.glob(os.path.join(basis, 'services', '*.py')) + glob.glob(os.path.join(basis, 'blueprints', '*.py'))
namen = set()
for pad in bestanden:
try:
with open(pad, encoding='utf-8', errors='ignore') as f:
namen.update(n for n in _RE.findall(f.read()) if n != n.lower())
except OSError:
continue
_namen = sorted(namen)
return _namen
def _q(cur, sql, params=()):
cur.execute(sql, params)
return cur.fetchall() if cur.with_rows else []
def herstel(conn, label):
"""Herstelt de tabelnamen in de database van `conn`. Geeft een lijst meldingen (leeg = niets gedaan)."""
meldingen = []
cur = conn.cursor()
try:
lctn = _q(cur, "SELECT @@lower_case_table_names")
if not lctn or int(lctn[0][0]) != 0:
return meldingen # Windows/macOS-gedrag: hoofdletters maken niet uit
bestaand = [r[0] for r in _q(cur, "SELECT TABLE_NAME FROM information_schema.TABLES "
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE'")]
if not bestaand:
return meldingen
per_klein = {}
for t in bestaand:
per_klein.setdefault(t.lower(), []).append(t)
te_doen = []
for naam in verwachte_namen():
varianten = [t for t in per_klein.get(naam.lower(), []) if t != naam]
if not varianten:
continue
if len(varianten) > 1:
meldingen.append(f"{label}: meerdere varianten van {naam} ({', '.join(varianten)}), niets gewijzigd")
continue
te_doen.append((naam, varianten[0], naam in per_klein.get(naam.lower(), [])))
if not te_doen:
return meldingen
aantal = lambda t: int(_q(cur, f"SELECT COUNT(*) FROM `{t}`")[0][0])
_q(cur, "SET FOREIGN_KEY_CHECKS = 0")
try:
for naam, oud, juiste_bestaat in te_doen:
try:
if juiste_bestaat:
n_juist, n_oud = aantal(naam), aantal(oud)
if n_oud == 0:
meldingen.append(f"{label}: {oud} is leeg en wordt niet gebruikt ({naam} wel); "
f"je mag {oud} verwijderen")
continue
if n_juist > 0:
meldingen.append(f"{label}: zowel {oud} ({n_oud} rijen) als {naam} ({n_juist} rijen) "
f"bevatten gegevens; niets gewijzigd, voeg ze zelf samen")
continue
_q(cur, f"DROP TABLE `{naam}`")
_q(cur, f"RENAME TABLE `{oud}` TO `{naam}`")
meldingen.append(f"{label}: lege {naam} vervangen door {oud} ({n_oud} rijen)")
else:
_q(cur, f"RENAME TABLE `{oud}` TO `{naam}`")
meldingen.append(f"{label}: {oud} hernoemd naar {naam}")
except Exception as e:
meldingen.append(f"{label}: {oud} → {naam} mislukt: {e}")
finally:
_q(cur, "SET FOREIGN_KEY_CHECKS = 1")
conn.commit()
finally:
cur.close()
return meldingen
def _verbinding(host, user, password, database):
"""Eén losse verbinding (geen pool): bij het opstarten zou een pool per school anders meteen
5 verbindingen per school openen en blijven vasthouden ("Too many connections")."""
import mysql.connector
from config import Config
return mysql.connector.connect(host=host, port=Config.DB_PORT, user=user, password=password, database=database,
connection_timeout=10, charset='utf8mb4', collation='utf8mb4_unicode_ci')
def herstel_centraal():
from config import Config
conn = _verbinding(Config.DB_HOST, Config.DB_USER, Config.DB_PASSWORD, Config.DB_CENTRAAL)
try:
return _log(herstel(conn, 'centrale database'))
finally:
conn.close()
def herstel_school(school):
cfg = school.db_config()
conn = _verbinding(cfg['host'], cfg['user'], cfg['password'], cfg['database'])
try:
return _log(herstel(conn, f"school {school.code}"))
finally:
conn.close()
def herstel_alle_scholen():
from tenancy import alle_scholen
uit = []
for school in alle_scholen(ook_inactief=True):
try:
uit += herstel_school(school)
except Exception as e:
uit += _log([f"school {school.code}: tabelnamen nakijken mislukt: {e}"])
return uit
def _log(meldingen):
for m in meldingen:
print(f"PIVOT tabelnamen: {m}", file=sys.stderr, flush=True)
return meldingen