importer 2
All checks were successful
Deploy / deploy-staging (push) Successful in 6s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-04-11 03:09:09 +02:00
parent 7400caa687
commit 35a0b10d0c
3 changed files with 214 additions and 3 deletions

View File

@@ -103,6 +103,16 @@ final class Router
], 201);
}
if ($resource === 'sql-import' && $method === 'POST') {
if (!isset($_FILES['sql_file']) || !is_array($_FILES['sql_file'])) {
throw new ApiException('Feld sql_file fehlt.', 422);
}
$this->respond([
'data' => $this->schemaManager()->importSqlFile($_FILES['sql_file']),
], 201);
}
if ($resource === 'upgrade' && $method === 'POST') {
$this->respond([
'data' => $this->schemaManager()->upgradeSchemaDirect(),

View File

@@ -365,6 +365,41 @@ final class SchemaManager
];
}
public function importSqlFile(array $uploadedFile): array
{
$errorCode = (int) ($uploadedFile['error'] ?? UPLOAD_ERR_NO_FILE);
if ($errorCode !== UPLOAD_ERR_OK) {
throw new ApiException(
'SQL-Datei konnte nicht hochgeladen werden.',
422,
['upload_error' => $errorCode]
);
}
$originalName = (string) ($uploadedFile['name'] ?? 'import.sql');
$tmpPath = (string) ($uploadedFile['tmp_name'] ?? '');
if ($tmpPath === '' || !is_uploaded_file($tmpPath)) {
throw new ApiException('Ungueltige Upload-Datei fuer SQL-Import.', 422);
}
if (!preg_match('/\.sql$/i', $originalName)) {
throw new ApiException('Bitte eine SQL-Datei mit Endung .sql hochladen.', 422, ['file' => $originalName]);
}
$sql = @file_get_contents($tmpPath);
if (!is_string($sql) || trim($sql) === '') {
throw new ApiException('Die hochgeladene SQL-Datei ist leer oder konnte nicht gelesen werden.', 422, ['file' => $originalName]);
}
$statementCount = $this->executeSqlStatements($this->splitSqlStatements($sql), $originalName);
return [
'file' => $originalName,
'statement_count' => $statementCount,
'message' => 'SQL-Datei wurde erfolgreich eingespielt.',
];
}
private function tableExists(string $table): bool
{
return in_array($table, $this->existingTables([$table]), true);
@@ -378,7 +413,14 @@ final class SchemaManager
}
$sql = (string) file_get_contents($schemaFile);
$statements = preg_split('/;\s*(?:\R|$)/', $sql) ?: [];
$this->executeSqlStatements($this->splitSqlStatements($sql), $schemaFile, 'Schema-Import fuer Mining-Checker fehlgeschlagen.');
}
/**
* @param list<string> $statements
*/
private function executeSqlStatements(array $statements, string $sourceLabel, string $errorMessage = 'SQL-Import fuer Mining-Checker fehlgeschlagen.'): int
{
$currentStatement = null;
$useTransaction = $this->driver === 'pgsql' && !$this->pdo->inTransaction();
@@ -387,35 +429,140 @@ final class SchemaManager
$this->pdo->beginTransaction();
}
$executed = 0;
foreach ($statements as $statement) {
$trimmed = trim($statement);
if ($trimmed === '') {
continue;
}
$currentStatement = $trimmed;
$this->pdo->exec($trimmed);
$executed++;
}
if ($useTransaction && $this->pdo->inTransaction()) {
$this->pdo->commit();
}
return $executed;
} catch (\Throwable $exception) {
if ($useTransaction && $this->pdo->inTransaction()) {
$this->pdo->rollBack();
}
throw new ApiException(
'Schema-Import fuer Mining-Checker fehlgeschlagen.',
$errorMessage,
500,
[
'message' => $exception->getMessage(),
'schema_file' => $schemaFile,
'source' => $sourceLabel,
'statement' => $currentStatement !== null ? substr($currentStatement, 0, 1000) : null,
]
);
}
}
/**
* @return list<string>
*/
private function splitSqlStatements(string $sql): array
{
$statements = [];
$buffer = '';
$length = strlen($sql);
$inSingleQuote = false;
$inDoubleQuote = false;
$inBacktickQuote = false;
$inLineComment = false;
$inBlockComment = false;
for ($index = 0; $index < $length; $index++) {
$char = $sql[$index];
$next = $index + 1 < $length ? $sql[$index + 1] : '';
if ($inLineComment) {
if ($char === "\n") {
$inLineComment = false;
$buffer .= $char;
}
continue;
}
if ($inBlockComment) {
if ($char === '*' && $next === '/') {
$inBlockComment = false;
$index++;
}
continue;
}
if (!$inSingleQuote && !$inDoubleQuote && !$inBacktickQuote) {
if ($char === '-' && $next === '-') {
$afterNext = $index + 2 < $length ? $sql[$index + 2] : '';
if ($afterNext === '' || ctype_space($afterNext)) {
$inLineComment = true;
$index++;
continue;
}
}
if ($char === '#') {
$inLineComment = true;
continue;
}
if ($char === '/' && $next === '*') {
$inBlockComment = true;
$index++;
continue;
}
}
if ($char === "'" && !$inDoubleQuote && !$inBacktickQuote) {
$escaped = $index > 0 && $sql[$index - 1] === '\\';
if (!$escaped) {
$inSingleQuote = !$inSingleQuote;
}
$buffer .= $char;
continue;
}
if ($char === '"' && !$inSingleQuote && !$inBacktickQuote) {
$escaped = $index > 0 && $sql[$index - 1] === '\\';
if (!$escaped) {
$inDoubleQuote = !$inDoubleQuote;
}
$buffer .= $char;
continue;
}
if ($char === '`' && !$inSingleQuote && !$inDoubleQuote) {
$inBacktickQuote = !$inBacktickQuote;
$buffer .= $char;
continue;
}
if ($char === ';' && !$inSingleQuote && !$inDoubleQuote && !$inBacktickQuote) {
$trimmed = trim($buffer);
if ($trimmed !== '') {
$statements[] = $trimmed;
}
$buffer = '';
continue;
}
$buffer .= $char;
}
$trimmed = trim($buffer);
if ($trimmed !== '') {
$statements[] = $trimmed;
}
return $statements;
}
private function dropExistingTables(): array
{
$tables = array_reverse($this->schemaStatus()['present_tables']);