""" 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