From 525e0a56af34b8c85666a87673ffdb5a3fff4777 Mon Sep 17 00:00:00 2001 From: busya Date: Fri, 29 May 2026 15:31:33 +0300 Subject: [PATCH] fix(translate): add database_id to datasources response + auto-select on frontend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: fetch_available_datasources did not return database_id, so frontend could not auto-set target_database_id when user selected a datasource. User then had to manually pick a database on Target Config tab — and could accidentally select the wrong one. Backend changes: - Add 'database_id' field to datasources response (from db_info.get('id')) - Add _database_name tracking in SupersetSqlLabExecutor with getter - Add database_name + database_backend to TargetSchemaValidationResponse - Enrich resolve_database_id logging with database_name and all_keys Frontend changes: - In selectDatasource(), auto-set targetDatabaseId from ds.database_id when present, so the correct target DB is used for schema checks. --- .../translate/__tests__/test_target_schema.py | 10 ++++++++++ backend/src/plugins/translate/service.py | 1 + .../plugins/translate/service_target_schema.py | 6 +++++- .../src/plugins/translate/superset_executor.py | 16 +++++++++++++++- backend/src/schemas/translate.py | 2 ++ frontend/src/routes/translate/[id]/+page.svelte | 4 ++++ 6 files changed, 37 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/translate/__tests__/test_target_schema.py b/backend/src/plugins/translate/__tests__/test_target_schema.py index 07fb0a51..0970bf1a 100644 --- a/backend/src/plugins/translate/__tests__/test_target_schema.py +++ b/backend/src/plugins/translate/__tests__/test_target_schema.py @@ -279,6 +279,7 @@ class TestValidateTargetTableSchema: instance = mock_executor.return_value instance.resolve_database_id.return_value = None instance.get_database_backend.return_value = "postgresql" + instance.get_database_name.return_value = "test_db" instance.execute_and_poll.return_value = { "status": "success", "raw_response": { @@ -299,6 +300,8 @@ class TestValidateTargetTableSchema: assert len(response.missing_columns) == 0 assert len(response.expected_columns) == 4 assert len(response.actual_columns) == 4 + assert response.database_name == "test_db" + assert response.database_backend == "postgresql" @patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor") def test_missing_columns(self, mock_executor, build_request) -> None: @@ -309,6 +312,7 @@ class TestValidateTargetTableSchema: instance = mock_executor.return_value instance.resolve_database_id.return_value = None instance.get_database_backend.return_value = "postgresql" + instance.get_database_name.return_value = "test_db" instance.execute_and_poll.return_value = { "status": "success", "raw_response": { @@ -343,6 +347,7 @@ class TestValidateTargetTableSchema: instance = mock_executor.return_value instance.resolve_database_id.return_value = None instance.get_database_backend.return_value = "clickhouse" + instance.get_database_name.return_value = "test_clickhouse" instance.execute_and_poll.return_value = { "status": "success", "raw_response": { @@ -366,6 +371,8 @@ class TestValidateTargetTableSchema: assert response.all_present is True assert len(response.missing_columns) == 0 assert len(response.actual_columns) == 8 + assert response.database_name == "test_clickhouse" + assert response.database_backend == "clickhouse" @patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor") def test_superset_failed_status(self, mock_executor, build_request) -> None: @@ -376,6 +383,7 @@ class TestValidateTargetTableSchema: instance = mock_executor.return_value instance.resolve_database_id.return_value = None instance.get_database_backend.return_value = "postgresql" + instance.get_database_name.return_value = "test_db" instance.execute_and_poll.return_value = { "status": "failed", "error_message": "Query timeout", @@ -396,6 +404,7 @@ class TestValidateTargetTableSchema: instance = mock_executor.return_value instance.resolve_database_id.return_value = None instance.get_database_backend.return_value = "postgresql" + instance.get_database_name.return_value = "test_db" instance.execute_and_poll.side_effect = ConnectionError("Superset API unavailable") response = validate_target_table_schema(req, config_manager) @@ -435,6 +444,7 @@ class TestValidateTargetTableSchema: instance = mock_executor.return_value instance.resolve_database_id.return_value = None instance.get_database_backend.return_value = "postgresql" + instance.get_database_name.return_value = "test_db" instance.execute_and_poll.return_value = { "status": "success", "raw_response": { diff --git a/backend/src/plugins/translate/service.py b/backend/src/plugins/translate/service.py index 6bba804c..91d40e09 100644 --- a/backend/src/plugins/translate/service.py +++ b/backend/src/plugins/translate/service.py @@ -254,6 +254,7 @@ class TranslateJobService: "table_name": name, "schema": ds.get("schema"), "database_name": db_info.get("database_name", "Unknown"), + "database_id": db_info.get("id"), # <-- добавлено: ID БД для target_database_id "database_dialect": dialect, "description": ds.get("description", ""), }) diff --git a/backend/src/plugins/translate/service_target_schema.py b/backend/src/plugins/translate/service_target_schema.py index ef8f5d22..9a5fc381 100644 --- a/backend/src/plugins/translate/service_target_schema.py +++ b/backend/src/plugins/translate/service_target_schema.py @@ -237,6 +237,7 @@ def validate_target_table_schema( executor = SupersetSqlLabExecutor(config_manager, req.environment_id) executor.resolve_database_id(target_database_id=req.target_database_id) backend = executor.get_database_backend() or "" + db_name = executor.get_database_name() or "unknown" safe_schema = (req.target_schema or "public").replace("'", "''") safe_table = req.target_table.replace("'", "''") @@ -284,7 +285,7 @@ def validate_target_table_schema( ) logger.reason("Executing SQL Lab query", - extra={"payload": {"sql": sql[:200], "backend": backend}}) + extra={"payload": {"sql": sql[:200], "backend": backend, "database_name": db_name}}) result = executor.execute_and_poll(sql=sql, max_polls=15, poll_interval_seconds=1.0) status = result.get("status", "") @@ -297,6 +298,7 @@ def validate_target_table_schema( table_exists=False, error=f"Superset SQL Lab error: {error_msg}", expected_columns=expected, actual_columns=[], missing_columns=expected, extra_columns=[], all_present=False, + database_name=db_name, database_backend=backend, ) # Парсим data-строки результата (реальные колонки таблицы) @@ -333,6 +335,7 @@ def validate_target_table_schema( missing_columns=missing, extra_columns=extra, all_present=len(missing) == 0, + database_name=db_name, database_backend=backend, ) except Exception as e: @@ -343,6 +346,7 @@ def validate_target_table_schema( table_exists=False, error=f"Failed to validate target schema: {e}", expected_columns=expected, actual_columns=[], missing_columns=expected, extra_columns=[], all_present=False, + database_name=db_name, database_backend=backend, ) # #endregion validate_target_table_schema # #endregion TargetSchemaValidation diff --git a/backend/src/plugins/translate/superset_executor.py b/backend/src/plugins/translate/superset_executor.py index a39eb7a5..0e13f573 100644 --- a/backend/src/plugins/translate/superset_executor.py +++ b/backend/src/plugins/translate/superset_executor.py @@ -32,6 +32,7 @@ class SupersetSqlLabExecutor: self._client: SupersetClient | None = None self._database_id: int | None = None self._database_backend: str | None = None + self._database_name: str | None = None # region _get_client [TYPE Function] # @PURPOSE Lazy-initialize SupersetClient for the configured environment. @@ -74,10 +75,13 @@ class SupersetSqlLabExecutor: result = db_info.get("result", db_info) self._database_id = result.get("id", db_id) self._database_backend = result.get("backend") or result.get("engine") + self._database_name = result.get("database_name", "") logger.reason("Resolved database ID by target_database_id", { "target_database_id": target_database_id, "database_id": self._database_id, + "database_name": self._database_name, "backend": self._database_backend, + "all_keys": list(result.keys()), }) return self._database_id except Exception as e: @@ -99,9 +103,11 @@ class SupersetSqlLabExecutor: if db.get("database_name", "").lower() == database_name.lower(): self._database_id = db["id"] self._database_backend = db.get("backend") + self._database_name = db.get("database_name", "") logger.reason("Resolved database ID by name", { "database_name": database_name, "database_id": self._database_id, + "actual_name": self._database_name, "backend": self._database_backend, }) return self._database_id @@ -112,8 +118,9 @@ class SupersetSqlLabExecutor: # Default: use first database self._database_id = databases[0]["id"] self._database_backend = databases[0].get("backend") + self._database_name = databases[0].get("database_name", "") logger.reason("Using default database", { - "database_name": databases[0].get("database_name"), + "database_name": self._database_name, "database_id": self._database_id, "backend": self._database_backend, }) @@ -127,6 +134,13 @@ class SupersetSqlLabExecutor: return self._database_backend # endregion get_database_backend + # region get_database_name [TYPE Function] + # @PURPOSE Return the cached database name. + # @POST Returns database name string or None if not yet resolved. + def get_database_name(self) -> str | None: + return self._database_name + # endregion get_database_name + # region execute_sql [TYPE Function] # @PURPOSE Submit SQL to Superset SQL Lab and return execution tracking info. # @PRE sql is valid SQL string. database_id is a valid Superset DB ID. diff --git a/backend/src/schemas/translate.py b/backend/src/schemas/translate.py index 02335ba6..e1820714 100644 --- a/backend/src/schemas/translate.py +++ b/backend/src/schemas/translate.py @@ -705,6 +705,8 @@ class TargetSchemaValidationResponse(BaseModel): missing_columns: list[TargetSchemaColumnInfo] = Field(default_factory=list, description="Expected columns NOT found in target table") extra_columns: list[TargetSchemaColumnInfo] = Field(default_factory=list, description="Actual columns NOT in expected list") all_present: bool = False + database_name: str | None = Field(default=None, description="Actual database name queried (from Superset)") + database_backend: str | None = Field(default=None, description="Actual database backend dialect (from Superset)") # #endregion TargetSchemaValidationResponse diff --git a/frontend/src/routes/translate/[id]/+page.svelte b/frontend/src/routes/translate/[id]/+page.svelte index 336091f4..230a8cba 100644 --- a/frontend/src/routes/translate/[id]/+page.svelte +++ b/frontend/src/routes/translate/[id]/+page.svelte @@ -462,6 +462,10 @@ targetSourceLanguageColumn = ''; contextColumns = []; databaseDialect = ds.database_dialect || ''; + // Auto-set target database ID from the selected datasource's database + if (ds.database_id) { + targetDatabaseId = String(ds.database_id); + } if (datasourceId && environmentId) { loadColumnsForDatasource(); }