setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $pdo->exec('PRAGMA foreign_keys = ON'); $pdo->exec(convert_schema_to_sqlite($schema)); $result = run_migrations($pdo, true); if ($result['errors']) { fail('SQLite-Migrationen fehlgeschlagen: ' . implode('; ', $result['errors'])); } $secondRun = run_migrations($pdo, true); if ($secondRun['errors'] || $secondRun['applied'] !== 0) { fail('SQLite-Migrationen sind bei wiederholter Ausführung nicht idempotent.'); } foreach ([ ['push_subscriptions', 'staff_id', 'staff'], ['ticket_thread_comments', 'thread_id', 'ticket_threads'], ['ticket_thread_comments', 'staff_id', 'staff'], ] as [$table, $column, $parent]) { if (!sqlite_has_foreign_key($pdo, $table, $column, $parent)) { fail("SQLite-Migration erzeugt {$table}.{$column} ohne FK auf {$parent}."); } } $sqliteReportExpression = report_avg_response_hours_expression('sqlite'); $mysqlReportExpression = report_avg_response_hours_expression('mysql'); if (stripos($sqliteReportExpression, 'julianday') === false) { fail('Der SQLite-Reportausdruck verwendet keine SQLite-kompatible Stundenberechnung.'); } if (stripos($mysqlReportExpression, 'TIMESTAMPDIFF(SECOND') === false || stripos($mysqlReportExpression, 'julianday') !== false) { fail('Der MySQL/MariaDB-Reportausdruck ist nicht treibersicher.'); } $reportPdo = new PDO('sqlite::memory:'); $reportHours = $reportPdo->query( "SELECT {$sqliteReportExpression} FROM (SELECT '2026-08-03 08:00:00' AS created_at, '2026-08-03 09:30:00' AS first_responded_at) t" )->fetchColumn(); if (abs((float) $reportHours - 1.5) > 0.0001) { fail('Der SQLite-Reportausdruck berechnet Antwortstunden falsch.'); } $legacy = new PDO('sqlite::memory:'); $legacy->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $legacy->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $legacy->exec('PRAGMA foreign_keys = ON'); $legacy->exec('CREATE TABLE staff (id INTEGER PRIMARY KEY)'); $legacy->exec('CREATE TABLE ticket_threads (id INTEGER PRIMARY KEY)'); $legacy->exec("CREATE TABLE push_subscriptions ( id INTEGER PRIMARY KEY AUTOINCREMENT, staff_id INT NOT NULL, endpoint TEXT NOT NULL, p256dh TEXT NOT NULL, auth TEXT NOT NULL, created_at DATETIME NOT NULL )"); $legacy->exec("CREATE TABLE ticket_thread_comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, thread_id INT NOT NULL, staff_id INT NOT NULL, message TEXT NOT NULL, created_at DATETIME NOT NULL )"); $legacy->exec('INSERT INTO staff (id) VALUES (1)'); $legacy->exec('INSERT INTO ticket_threads (id) VALUES (1)'); $legacy->exec("INSERT INTO push_subscriptions VALUES (1, 1, 'endpoint', 'key', 'auth', CURRENT_TIMESTAMP)"); $legacy->exec("INSERT INTO ticket_thread_comments VALUES (1, 1, 1, 'Kommentar', CURRENT_TIMESTAMP)"); repair_sqlite_foreign_keys($legacy); if ((int) $legacy->query('SELECT COUNT(*) FROM push_subscriptions')->fetchColumn() !== 1 || (int) $legacy->query('SELECT COUNT(*) FROM ticket_thread_comments')->fetchColumn() !== 1) { fail('Die SQLite-FK-Reparatur bewahrt bestehende Daten nicht.'); } $legacy->exec('DELETE FROM staff WHERE id = 1'); if ((int) $legacy->query('SELECT COUNT(*) FROM push_subscriptions')->fetchColumn() !== 0 || (int) $legacy->query('SELECT COUNT(*) FROM ticket_thread_comments')->fetchColumn() !== 0) { fail('Die reparierten SQLite-FKs führen ON DELETE CASCADE nicht aus.'); } $forbiddenTokens = [ 'cron/process_backups.php' => ['INSERT OR REPLACE'], 'staff/backup.php' => ['INSERT OR REPLACE'], 'staff/ticket_presence.php' => ['INSERT OR REPLACE', "datetime('now'"], 'staff/availability.php' => ['INSERT OR REPLACE', 'REPLACE INTO'], 'cron/process_recurring.php' => ['DATE_ADD('], 'cron/check_escalations.php' => ["julianday('now')"], 'staff/recurring_tickets.php' => ['DATE_ADD(', 'CURDATE('], ]; foreach ($forbiddenTokens as $relativePath => $tokens) { $source = file_get_contents($root . '/' . $relativePath); if ($source === false) { fail("Datei {$relativePath} konnte nicht gelesen werden."); } foreach ($tokens as $token) { if (stripos($source, $token) !== false) { fail("{$relativePath} enthält weiterhin den fehlerhaften Dialekt-Token {$token}."); } } } echo "Datenbankdialekte und SQLite-Migrationen sind kompatibel.\n"; function fail(string $message): never { fwrite(STDERR, $message . "\n"); exit(1); }