importer 2
This commit is contained in:
@@ -549,6 +549,7 @@
|
||||
const [debugEntries, setDebugEntries] = useState([]);
|
||||
const [schemaStatus, setSchemaStatus] = useState(normalizeSchemaStatus(null));
|
||||
const [initForm, setInitForm] = useState({ drop_existing: false });
|
||||
const [sqlImportFile, setSqlImportFile] = useState(null);
|
||||
const [dbCheck, setDbCheck] = useState(null);
|
||||
const [measurementForm, setMeasurementForm] = useState({
|
||||
measured_at: '',
|
||||
@@ -1572,6 +1573,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function importSqlFile() {
|
||||
if (!sqlImportFile) {
|
||||
setError('Bitte zuerst eine SQL-Datei auswaehlen.');
|
||||
setMessage('');
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
setError('');
|
||||
setMessage('');
|
||||
try {
|
||||
const body = new FormData();
|
||||
body.append('sql_file', sqlImportFile);
|
||||
const result = await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/sql-import`, {
|
||||
method: 'POST',
|
||||
body,
|
||||
timeoutMs: 30000,
|
||||
});
|
||||
setSqlImportFile(null);
|
||||
setMessage(
|
||||
`${result.message || 'SQL-Datei wurde importiert.'} ` +
|
||||
`${result.statement_count || 0} Statements aus ${result.file || 'der Datei'} ausgefuehrt.`
|
||||
);
|
||||
await loadSchemaStatus(projectKey);
|
||||
await loadBootstrap(projectKey);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function testDatabaseConnection() {
|
||||
setSaving(true);
|
||||
setError('');
|
||||
@@ -2866,6 +2899,27 @@
|
||||
onClick: importOldData,
|
||||
disabled: saving,
|
||||
}, saving ? 'Importiert …' : 'Alte Daten importieren'),
|
||||
h('div', { key: 'sql-import', className: 'mc-form' }, [
|
||||
h('label', { className: 'mc-field' }, [
|
||||
h('span', { className: 'mc-field-label' }, 'SQL-Datei importieren'),
|
||||
h('input', {
|
||||
type: 'file',
|
||||
accept: '.sql,text/sql,application/sql',
|
||||
onChange: (event) => setSqlImportFile(event.target.files && event.target.files[0] ? event.target.files[0] : null),
|
||||
}),
|
||||
]),
|
||||
h('div', { className: 'mc-text' },
|
||||
sqlImportFile
|
||||
? `Ausgewaehlt: ${sqlImportFile.name}`
|
||||
: 'Fuehrt die ausgewaehlte SQL-Datei direkt in der aktuellen Projekt-Datenbank aus. Bestehende Daten werden dabei nicht automatisch geloescht.'
|
||||
),
|
||||
h('button', {
|
||||
type: 'button',
|
||||
className: 'mc-button mc-button--secondary',
|
||||
onClick: importSqlFile,
|
||||
disabled: saving || !sqlImportFile,
|
||||
}, saving ? 'Importiert …' : 'SQL-Datei einspielen'),
|
||||
]),
|
||||
]),
|
||||
panel('Datenbank-Test', 'Prueft, ob das Modul die Projekt-Datenbank erreichen und eine einfache Anfrage ausfuehren kann.', [
|
||||
dbCheck
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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']);
|
||||
|
||||
Reference in New Issue
Block a user