fix(translate): add database_id to datasources response + auto-select on frontend
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.
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -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", ""),
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user