Skip to content

Commit 93d91b2

Browse files
Merge pull request #73 from Queryus/develop
1차 개발 최종
2 parents 5624aa9 + e06ea0c commit 93d91b2

File tree

8 files changed

+332
-60
lines changed

8 files changed

+332
-60
lines changed

app/api/annotation_api.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from app.core.response import ResponseMessage
44
from app.core.status import CommonCode
5+
from app.schemas.annotation.hierarchical_response_model import HierarchicalDBMSAnnotation
56
from app.schemas.annotation.request_model import AnnotationCreateRequest
67
from app.schemas.annotation.response_model import AnnotationDeleteResponse, FullAnnotationResponse
78
from app.services.annotation_service import AnnotationService, annotation_service
@@ -59,6 +60,22 @@ def get_annotation_by_db_profile_id(
5960
return ResponseMessage.success(value=annotation, code=CommonCode.SUCCESS_FIND_ANNOTATION)
6061

6162

63+
@router.get(
64+
"/find/hierarchical/{db_profile_id}",
65+
response_model=ResponseMessage[HierarchicalDBMSAnnotation],
66+
summary="DB 프로필 ID로 계층적 어노테이션 조회",
67+
)
68+
def get_hierarchical_annotation_by_db_profile_id(
69+
db_profile_id: str,
70+
service: AnnotationService = annotation_service_dependency,
71+
) -> ResponseMessage[HierarchicalDBMSAnnotation]:
72+
"""
73+
`db_profile_id`에 연결된 어노테이션을 계층 구조(DBMS > DB > 스키마 > 테이블 > 컬럼)로 조회합니다.
74+
"""
75+
annotation = service.get_hierarchical_annotation_by_db_profile_id(db_profile_id)
76+
return ResponseMessage.success(value=annotation, code=CommonCode.SUCCESS_FIND_ANNOTATION)
77+
78+
6279
@router.delete(
6380
"/remove/{annotation_id}",
6481
response_model=ResponseMessage[AnnotationDeleteResponse],

app/core/status.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,12 @@ class CommonCode(Enum):
118118
"5002",
119119
"데이터 생성 후 검증 과정에서 에러가 발생했습니다.",
120120
)
121-
121+
FAIL_AI_SERVER_CONNECTION = (status.HTTP_503_SERVICE_UNAVAILABLE, "5003", "AI 서버 연결에 실패했습니다.")
122+
FAIL_AI_SERVER_PROCESSING = (
123+
status.HTTP_500_INTERNAL_SERVER_ERROR,
124+
"5004",
125+
"AI 서버가 요청을 처리하는 데 실패했습니다.",
126+
)
122127
""" DRIVER, DB 서버 에러 코드 - 51xx """
123128
FAIL_CONNECT_DB = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5100", "디비 연결 중 에러가 발생했습니다.")
124129
FAIL_FIND_PROFILE = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5101", "디비 정보 조회 중 에러가 발생했습니다.")
@@ -154,12 +159,6 @@ class CommonCode(Enum):
154159
FAIL_CREATE_ANNOTATION = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5400", "어노테이션 생성 중 에러가 발생했습니다.")
155160
FAIL_FIND_ANNOTATION = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5401", "어노테이션 조회 중 에러가 발생했습니다.")
156161
FAIL_DELETE_ANNOTATION = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5402", "어노테이션 삭제 중 에러가 발생했습니다.")
157-
FAIL_AI_SERVER_CONNECTION = (status.HTTP_503_SERVICE_UNAVAILABLE, "5403", "AI 서버 연결에 실패했습니다.")
158-
FAIL_AI_SERVER_PROCESSING = (
159-
status.HTTP_500_INTERNAL_SERVER_ERROR,
160-
"5404",
161-
"AI 서버가 요청을 처리하는 데 실패했습니다.",
162-
)
163162

164163
""" SQL 서버 에러 코드 - 55xx """
165164

app/repository/annotation_repository.py

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import logging
12
import sqlite3
23

4+
from app.core.exceptions import APIException
5+
from app.core.status import CommonCode
36
from app.core.utils import get_db_path
47
from app.schemas.annotation.db_model import (
58
ColumnAnnotationInDB,
@@ -10,6 +13,13 @@
1013
TableAnnotationInDB,
1114
TableConstraintInDB,
1215
)
16+
from app.schemas.annotation.hierarchical_response_model import (
17+
HierarchicalColumnAnnotation,
18+
HierarchicalDBAnnotation,
19+
HierarchicalDBMSAnnotation,
20+
HierarchicalRelationshipAnnotation,
21+
HierarchicalTableAnnotation,
22+
)
1323
from app.schemas.annotation.response_model import (
1424
ColumnAnnotationDetail,
1525
ConstraintDetail,
@@ -99,7 +109,7 @@ def create_full_annotation(
99109
(
100110
c.id,
101111
c.table_annotation_id,
102-
c.constraint_type,
112+
c.constraint_type.value,
103113
c.name,
104114
c.description,
105115
c.expression,
@@ -279,6 +289,139 @@ def find_full_annotation_by_id(self, annotation_id: str) -> FullAnnotationRespon
279289
if conn:
280290
conn.close()
281291

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+
282425
def delete_annotation_by_id(self, annotation_id: str) -> bool:
283426
"""
284427
annotationId로 특정 어노테이션을 삭제합니다.

app/repository/chat_tab_repository.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def updated_chat_tab(self, id: str, new_name: str | None) -> ChatTabInDB | None:
5656

5757
# 데이터 업데이트
5858
cursor.execute(
59-
"UPDATE chat_tab SET name = ?, updated_at = datetime('now', 'localtime') WHERE id = ?",
59+
"UPDATE chat_tab SET name = ?, updated_at = datetime('now') WHERE id = ?",
6060
(new_name, id),
6161
)
6262
conn.commit()
@@ -73,6 +73,20 @@ def updated_chat_tab(self, id: str, new_name: str | None) -> ChatTabInDB | None:
7373
if conn:
7474
conn.close()
7575

76+
def update_tab_timestamp(self, id: str) -> bool:
77+
"""지정된 ID의 채팅 탭의 updated_at 타임스탬프를 현재 시간으로 업데이트합니다."""
78+
db_path = get_db_path()
79+
conn = None
80+
try:
81+
conn = sqlite3.connect(str(db_path), timeout=10)
82+
cursor = conn.cursor()
83+
cursor.execute("UPDATE chat_tab SET updated_at = datetime('now') WHERE id = ?", (id,))
84+
conn.commit()
85+
return cursor.rowcount > 0
86+
finally:
87+
if conn:
88+
conn.close()
89+
7690
def delete_chat_tab(self, id: str) -> bool:
7791
"""채팅 탭ID에 해당하는 ChatTab을 삭제하고, 성공 여부를 반환합니다."""
7892
db_path = get_db_path()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from datetime import datetime
2+
3+
from pydantic import BaseModel, Field
4+
5+
6+
class HierarchicalColumnAnnotation(BaseModel):
7+
column_name: str = Field(..., description="컬럼 이름")
8+
description: str | None = Field(None, description="AI가 생성한 컬럼 어노테이션")
9+
data_type: str | None = Field(None, description="컬럼 데이터 타입")
10+
11+
12+
class HierarchicalTableAnnotation(BaseModel):
13+
table_name: str = Field(..., description="테이블 이름")
14+
description: str | None = Field(None, description="AI가 생성한 테이블 어노테이션")
15+
columns: list[HierarchicalColumnAnnotation] = Field(..., description="테이블에 속한 컬럼 목록")
16+
17+
18+
class HierarchicalRelationshipAnnotation(BaseModel):
19+
from_table: str = Field(..., description="외래 키 제약조건이 시작되는 테이블")
20+
from_columns: list[str] = Field(..., description="외래 키에 포함된 컬럼들")
21+
to_table: str = Field(..., description="외래 키가 참조하는 테이블")
22+
to_columns: list[str] = Field(..., description="참조되는 테이블의 컬럼들")
23+
description: str | None = Field(None, description="AI가 생성한 관계 어노테이션")
24+
25+
26+
class HierarchicalDBAnnotation(BaseModel):
27+
db_name: str = Field(..., description="데이터베이스 이름")
28+
description: str | None = Field(None, description="AI가 생성한 데이터베이스 어노테이션")
29+
tables: list[HierarchicalTableAnnotation] = Field(..., description="데이터베이스에 속한 테이블 목록")
30+
relationships: list[HierarchicalRelationshipAnnotation] = Field(..., description="테이블 간의 관계 목록")
31+
32+
33+
class HierarchicalDBMSAnnotation(BaseModel):
34+
dbms_type: str = Field(..., description="DBMS 종류 (e.g., postgresql, oracle)")
35+
databases: list[HierarchicalDBAnnotation] = Field(..., description="DBMS에 속한 데이터베이스 목록")
36+
annotation_id: str = Field(..., description="최상위 어노테이션 ID")
37+
db_profile_id: str = Field(..., description="DB 프로필 ID")
38+
created_at: datetime = Field(..., description="생성 일시")
39+
updated_at: datetime = Field(..., description="수정 일시")

app/schemas/chat_message/base_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ def validate_chat_tab_id(self, field_value: str) -> None:
2929
def validate_message(self, field_value: str) -> None:
3030
"""메시지 유효성 검사"""
3131
if not field_value or field_value.strip() == "":
32-
raise APIException(CommonCode.INVALID_ANNOTATION_REQUEST)
32+
raise APIException(CommonCode.INVALID_CHAT_MESSAGE_REQUEST)

0 commit comments

Comments
 (0)