diff --git a/migrate.php b/migrate.php index 233be75..6432a3e 100644 --- a/migrate.php +++ b/migrate.php @@ -163,7 +163,96 @@ function splitSqlStatements(string $sql): array return $statements; } -function isIgnorableMigrationError(PDOException $exception): bool +function tableExists(PDO $pdo, string $tableName): bool +{ + static $cache = []; + + $normalized = strtolower($tableName); + if (array_key_exists($normalized, $cache)) { + return $cache[$normalized]; + } + + $stmt = $pdo->prepare('SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table LIMIT 1'); + $stmt->execute(['table' => $tableName]); + + $cache[$normalized] = (bool) $stmt->fetchColumn(); + return $cache[$normalized]; +} + +function schemaDefinesTable(string $tableName): bool +{ + static $tables = null; + + if ($tables === null) { + $tables = []; + $schemaFiles = [ + __DIR__ . '/db/schema.sql', + __DIR__ . '/complete_schema.sql', + ]; + + foreach ($schemaFiles as $schemaFile) { + if (!is_file($schemaFile)) { + continue; + } + + $sql = file_get_contents($schemaFile); + if ($sql === false) { + continue; + } + + if (preg_match_all('/CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+`?([a-zA-Z0-9_]+)`?/i', $sql, $matches)) { + foreach ($matches[1] as $name) { + $tables[strtolower($name)] = true; + } + } + } + } + + return isset($tables[strtolower($tableName)]); +} + +function extractMissingTableName(PDOException $exception): ?string +{ + $message = $exception->getMessage(); + + if (preg_match("/Table '([^']+)' doesn't exist/i", $message, $matches)) { + $qualifiedName = str_replace('`', '', $matches[1]); + $parts = explode('.', $qualifiedName); + $tableName = trim((string) end($parts)); + return $tableName !== '' ? $tableName : null; + } + + return null; +} + +function statementMentionsTable(string $statement, string $tableName): bool +{ + $pattern = '/(^|[^a-zA-Z0-9_])`?' . preg_quote($tableName, '/') . '`?([^a-zA-Z0-9_]|$)/i'; + return preg_match($pattern, $statement) === 1; +} + +function isLegacyMissingTableError(PDO $pdo, PDOException $exception, string $statement): bool +{ + $driverCode = isset($exception->errorInfo[1]) ? (int) $exception->errorInfo[1] : null; + $message = strtolower($exception->getMessage()); + + if ($driverCode !== 1146 && !str_contains($message, 'base table or view not found')) { + return false; + } + + $missingTable = extractMissingTableName($exception); + if ($missingTable === null || !statementMentionsTable($statement, $missingTable)) { + return false; + } + + if (tableExists($pdo, $missingTable) || schemaDefinesTable($missingTable)) { + return false; + } + + return true; +} + +function isIgnorableMigrationError(PDO $pdo, PDOException $exception, string $statement): bool { $driverCode = isset($exception->errorInfo[1]) ? (int) $exception->errorInfo[1] : null; $message = strtolower($exception->getMessage()); @@ -189,6 +278,10 @@ function isIgnorableMigrationError(PDOException $exception): bool } } + if (isLegacyMissingTableError($pdo, $exception, $statement)) { + return true; + } + return false; } @@ -209,7 +302,7 @@ function executeSqlMigration(PDO $pdo, string $filePath): void try { $pdo->exec($statement); } catch (PDOException $exception) { - if (isIgnorableMigrationError($exception)) { + if (isIgnorableMigrationError($pdo, $exception, $statement)) { migrationOutput(' - skipped statement ' . ($number + 1) . ': ' . $exception->getMessage()); continue; }