From b5282df56f89dcae135961c069cbaa7bc0db087d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cmit=20Tun=C3=A7?= Date: Fri, 24 Apr 2026 22:45:20 +0300 Subject: [PATCH] feat: implement MySQL driver and schema discovery service for dynamic database management --- .../Http/Controllers/Api/SchemaController.php | 4 +- backend/app/Services/Database/MySqlDriver.php | 58 ++++++++++++++++++- backend/app/Services/DatabaseService.php | 4 +- frontend/src/components/MainContent.tsx | 2 +- frontend/src/services/api.ts | 6 +- 5 files changed, 68 insertions(+), 6 deletions(-) diff --git a/backend/app/Http/Controllers/Api/SchemaController.php b/backend/app/Http/Controllers/Api/SchemaController.php index 7fe2471..02e14e9 100644 --- a/backend/app/Http/Controllers/Api/SchemaController.php +++ b/backend/app/Http/Controllers/Api/SchemaController.php @@ -111,7 +111,9 @@ class SchemaController extends Controller try { $this->initializeDriver($request); $config = $request->only(['host', 'username', 'password', 'database', 'port', 'table']); - $filePath = $this->databaseService->export($config); + $filters = json_decode($request->get('filters', '[]'), true); + + $filePath = $this->databaseService->export($config, $filters); return Response::download($filePath)->deleteFileAfterSend(true); } catch (\Exception $e) { diff --git a/backend/app/Services/Database/MySqlDriver.php b/backend/app/Services/Database/MySqlDriver.php index 62e4eeb..18ab252 100644 --- a/backend/app/Services/Database/MySqlDriver.php +++ b/backend/app/Services/Database/MySqlDriver.php @@ -147,11 +147,16 @@ class MySqlDriver implements DatabaseDriverInterface, SchemaDiscoveryInterface return $this->query($sql, [$table, $dbName]); } - public function export(array $config): string + public function export(array $config, array $filters = []): string { $database = $config['database'] ?? ''; $table = $config['table'] ?? ''; + // If filters are provided, we do a manual export for the filtered rows + if (!empty($filters) && !empty($table)) { + return $this->exportFilteredTable($database, $table, $filters); + } + $filename = !empty($table) ? "{$table}-" . date('Y-m-d') . ".sql" : "backup-" . ($database ?: 'all') . "-" . date('Y-m-d') . ".sql"; @@ -392,4 +397,55 @@ class MySqlDriver implements DatabaseDriverInterface, SchemaDiscoveryInterface throw $e; } } + + protected function exportFilteredTable(string $database, string $table, array $filters): string + { + $query = DB::connection($this->connectionName)->table($table); + $this->applyFilters($query, $filters); + $rows = $query->get(); + + $filename = "{$table}-filtered-" . date('Y-m-d-His') . ".sql"; + $directory = storage_path('app/backups'); + if (!is_dir($directory)) { + mkdir($directory, 0755, true); + } + $path = $directory . DIRECTORY_SEPARATOR . $filename; + + $handle = fopen($path, 'w'); + + fwrite($handle, "-- Mariavel Filtered SQL Export\n"); + fwrite($handle, "-- Database: {$database}\n"); + fwrite($handle, "-- Table: {$table}\n"); + fwrite($handle, "-- Date: " . date('Y-m-d H:i:s') . "\n\n"); + + $createTableResults = $this->query("SHOW CREATE TABLE `{$table}`"); + if (!empty($createTableResults)) { + $createTable = $createTableResults[0]; + $createSql = $createTable->{'Create Table'} ?? ''; + if ($createSql) { + fwrite($handle, "DROP TABLE IF EXISTS `{$table}`;\n"); + fwrite($handle, $createSql . ";\n\n"); + } + } + + if ($rows->count() > 0) { + foreach ($rows as $row) { + $fields = []; + $values = []; + foreach ((array)$row as $field => $value) { + $fields[] = "`{$field}`"; + if (is_null($value)) { + $values[] = "NULL"; + } else { + $values[] = DB::connection($this->connectionName)->getPdo()->quote($value); + } + } + $sql = "INSERT INTO `{$table}` (" . implode(', ', $fields) . ") VALUES (" . implode(', ', $values) . ");\n"; + fwrite($handle, $sql); + } + } + + fclose($handle); + return $path; + } } diff --git a/backend/app/Services/DatabaseService.php b/backend/app/Services/DatabaseService.php index a582154..758e502 100644 --- a/backend/app/Services/DatabaseService.php +++ b/backend/app/Services/DatabaseService.php @@ -102,9 +102,9 @@ class DatabaseService /** * Export the database. */ - public function export(array $config): string + public function export(array $config, array $filters = []): string { - return $this->getDriver()->export($config); + return $this->getDriver()->export($config, $filters); } /** diff --git a/frontend/src/components/MainContent.tsx b/frontend/src/components/MainContent.tsx index f8dfd7e..6c0fa7b 100644 --- a/frontend/src/components/MainContent.tsx +++ b/frontend/src/components/MainContent.tsx @@ -116,7 +116,7 @@ const MainContent: React.FC = () => { // For now, we'll use the existing SQL export for 'sql' // and implement a client-side CSV export for 'csv' since we have the rows if (format === 'sql') { - const response = await SchemaService.exportDatabase(activeDatabase, activeTable); + const response = await SchemaService.exportDatabase(activeDatabase, activeTable, filterModel.items); const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = url; diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 77f8d67..8a72d80 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -36,7 +36,11 @@ export const SchemaService = { bulkAction: (data: { tables: string[], action: string, database: string }) => api.post('/schema/bulk-action', data), batchUpdate: (table: string, changes: any[]) => api.post(`/schema/${table}/batch-update`, { changes }), executeQuery: (query: string) => api.post('/schema/execute', { query }), - exportDatabase: (database?: string, table?: string) => api.post('/schema/export', { database, table }, { responseType: 'blob' }), + exportDatabase: (database?: string, table?: string, filters?: any) => api.post('/schema/export', { + database, + table, + filters: filters ? JSON.stringify(filters) : undefined + }, { responseType: 'blob' }), importDatabase: (formData: FormData) => api.post('/schema/import', formData, { headers: { 'Content-Type': 'multipart/form-data' } }),