|
| 1 | +import logging |
1 | 2 | import sqlite3 |
2 | 3 |
|
| 4 | +from app.core.exceptions import APIException |
| 5 | +from app.core.status import CommonCode |
3 | 6 | from app.core.utils import get_db_path |
4 | 7 | from app.schemas.annotation.db_model import ( |
5 | 8 | ColumnAnnotationInDB, |
|
10 | 13 | TableAnnotationInDB, |
11 | 14 | TableConstraintInDB, |
12 | 15 | ) |
| 16 | +from app.schemas.annotation.hierarchical_response_model import ( |
| 17 | + HierarchicalColumnAnnotation, |
| 18 | + HierarchicalDBAnnotation, |
| 19 | + HierarchicalDBMSAnnotation, |
| 20 | + HierarchicalRelationshipAnnotation, |
| 21 | + HierarchicalTableAnnotation, |
| 22 | +) |
13 | 23 | from app.schemas.annotation.response_model import ( |
14 | 24 | ColumnAnnotationDetail, |
15 | 25 | ConstraintDetail, |
@@ -99,7 +109,7 @@ def create_full_annotation( |
99 | 109 | ( |
100 | 110 | c.id, |
101 | 111 | c.table_annotation_id, |
102 | | - c.constraint_type, |
| 112 | + c.constraint_type.value, |
103 | 113 | c.name, |
104 | 114 | c.description, |
105 | 115 | c.expression, |
@@ -279,6 +289,139 @@ def find_full_annotation_by_id(self, annotation_id: str) -> FullAnnotationRespon |
279 | 289 | if conn: |
280 | 290 | conn.close() |
281 | 291 |
|
| 292 | + def find_hierarchical_annotation_by_profile_id(self, db_profile_id: str) -> HierarchicalDBMSAnnotation | None: |
| 293 | + """ |
| 294 | + db_profile_id로 계층적 어노테이션 정보를 조회합니다. |
| 295 | + - DBMS > DB > 테이블 > 컬럼 구조로 데이터를 조립하여 반환합니다. |
| 296 | + """ |
| 297 | + db_path = get_db_path() |
| 298 | + conn = None |
| 299 | + try: |
| 300 | + conn = sqlite3.connect(str(db_path), timeout=10) |
| 301 | + conn.row_factory = sqlite3.Row |
| 302 | + cursor = conn.cursor() |
| 303 | + |
| 304 | + # 1. 기본 정보 조회 (db_profile, database_annotation) |
| 305 | + cursor.execute( |
| 306 | + """ |
| 307 | + SELECT |
| 308 | + dp.type as dbms_type, |
| 309 | + da.id as annotation_id, |
| 310 | + da.db_profile_id, |
| 311 | + da.database_name, |
| 312 | + da.description as db_description, |
| 313 | + da.created_at, |
| 314 | + da.updated_at |
| 315 | + FROM db_profile dp |
| 316 | + JOIN database_annotation da ON dp.annotation_id = da.id |
| 317 | + WHERE dp.id = ? |
| 318 | + """, |
| 319 | + (db_profile_id,), |
| 320 | + ) |
| 321 | + base_info = cursor.fetchone() |
| 322 | + if not base_info: |
| 323 | + return None |
| 324 | + |
| 325 | + # 2. 테이블 및 컬럼 정보 한번에 조회 |
| 326 | + cursor.execute( |
| 327 | + """ |
| 328 | + SELECT |
| 329 | + ta.id as table_id, |
| 330 | + ta.table_name, |
| 331 | + ta.description as table_description, |
| 332 | + ca.column_name, |
| 333 | + ca.description as column_description, |
| 334 | + ca.data_type |
| 335 | + FROM table_annotation ta |
| 336 | + JOIN column_annotation ca ON ta.id = ca.table_annotation_id |
| 337 | + WHERE ta.database_annotation_id = ? |
| 338 | + ORDER BY ta.table_name, ca.ordinal_position |
| 339 | + """, |
| 340 | + (base_info["annotation_id"],), |
| 341 | + ) |
| 342 | + rows = cursor.fetchall() |
| 343 | + |
| 344 | + # 3. 데이터 계층 구조로 조립 (테이블, 컬럼) |
| 345 | + tables_map = {} |
| 346 | + for row in rows: |
| 347 | + table_name = row["table_name"] |
| 348 | + if table_name not in tables_map: |
| 349 | + tables_map[table_name] = HierarchicalTableAnnotation( |
| 350 | + table_name=table_name, |
| 351 | + description=row["table_description"], |
| 352 | + columns=[], |
| 353 | + ) |
| 354 | + tables_map[table_name].columns.append( |
| 355 | + HierarchicalColumnAnnotation( |
| 356 | + column_name=row["column_name"], |
| 357 | + description=row["column_description"], |
| 358 | + data_type=row["data_type"], |
| 359 | + ) |
| 360 | + ) |
| 361 | + |
| 362 | + # 4. 관계 정보 조회 |
| 363 | + cursor.execute( |
| 364 | + """ |
| 365 | + SELECT |
| 366 | + ta_from.table_name as from_table, |
| 367 | + ca_from.column_name as from_column, |
| 368 | + tc.ref_table as to_table, |
| 369 | + cc.referenced_column_name as to_column, |
| 370 | + tc.name as constraint_name, |
| 371 | + tc.description as relationship_description |
| 372 | + FROM table_constraint tc |
| 373 | + JOIN table_annotation ta_from ON tc.table_annotation_id = ta_from.id |
| 374 | + JOIN constraint_column cc ON tc.id = cc.constraint_id |
| 375 | + JOIN column_annotation ca_from ON cc.column_annotation_id = ca_from.id |
| 376 | + WHERE ta_from.database_annotation_id = ? AND tc.constraint_type = 'FOREIGN KEY' |
| 377 | + ORDER BY tc.name, cc.position |
| 378 | + """, |
| 379 | + (base_info["annotation_id"],), |
| 380 | + ) |
| 381 | + relationship_rows = cursor.fetchall() |
| 382 | + logging.info(f"Raw relationship rows from DB: {[dict(row) for row in relationship_rows]}") |
| 383 | + |
| 384 | + relationships_map = {} |
| 385 | + for row in relationship_rows: |
| 386 | + constraint_name = row["constraint_name"] |
| 387 | + if constraint_name not in relationships_map: |
| 388 | + relationships_map[constraint_name] = { |
| 389 | + "from_table": row["from_table"], |
| 390 | + "to_table": row["to_table"], |
| 391 | + "description": row["relationship_description"], |
| 392 | + "from_columns": [], |
| 393 | + "to_columns": [], |
| 394 | + } |
| 395 | + relationships_map[constraint_name]["from_columns"].append(row["from_column"]) |
| 396 | + relationships_map[constraint_name]["to_columns"].append(row["to_column"]) |
| 397 | + |
| 398 | + logging.info(f"Processed relationships map: {relationships_map}") |
| 399 | + relationships = [HierarchicalRelationshipAnnotation(**data) for data in relationships_map.values()] |
| 400 | + logging.info(f"Final relationships list: {relationships}") |
| 401 | + |
| 402 | + # 5. 최종 데이터 조립 |
| 403 | + db = HierarchicalDBAnnotation( |
| 404 | + db_name=base_info["database_name"], |
| 405 | + description=base_info["db_description"], |
| 406 | + tables=list(tables_map.values()), |
| 407 | + relationships=relationships, |
| 408 | + ) |
| 409 | + |
| 410 | + return HierarchicalDBMSAnnotation( |
| 411 | + dbms_type=base_info["dbms_type"], |
| 412 | + databases=[db], |
| 413 | + annotation_id=base_info["annotation_id"], |
| 414 | + db_profile_id=base_info["db_profile_id"], |
| 415 | + created_at=base_info["created_at"], |
| 416 | + updated_at=base_info["updated_at"], |
| 417 | + ) |
| 418 | + |
| 419 | + except sqlite3.Error as e: |
| 420 | + raise APIException(CommonCode.FAIL_FIND_ANNOTATION) from e |
| 421 | + finally: |
| 422 | + if conn: |
| 423 | + conn.close() |
| 424 | + |
282 | 425 | def delete_annotation_by_id(self, annotation_id: str) -> bool: |
283 | 426 | """ |
284 | 427 | annotationId로 특정 어노테이션을 삭제합니다. |
|
0 commit comments