149 lines
6.3 KiB
Python
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
|