refs #703: fix Smilegate annotation metadata query
This commit is contained in:
@@ -54,7 +54,7 @@ MCP tool은 `SGMP_POC_HAIKU45` 프로파일을 기준으로 게임 데이터의
|
||||
|
||||
### 5. 스키마 메타데이터 접근은 MyBatis로 통일한다
|
||||
|
||||
`/schema-metadata`는 화면 카드 목록을 정적 허용 목록에서 만들고, 선택된 테이블의 comment·컬럼·annotation만 조회한다. 서비스 계층에는 JDBC 직접 실행을 두지 않는다. MyBatis mapper의 모든 사전 조회는 `owner = 'SGMP_POC'` 조건을 갖고, DDL에 쓰이는 테이블·컬럼·annotation 이름은 호출 전에 대문자 식별자 규칙과 허용 테이블 목록으로 검증한다.
|
||||
`/schema-metadata`는 화면 카드 목록을 정적 허용 목록에서 만들고, 선택된 테이블의 comment·컬럼·annotation만 조회한다. 서비스 계층에는 JDBC 직접 실행을 두지 않는다. table/column comment 사전 조회는 `owner = 'SGMP_POC'` 조건을 갖는다. Oracle의 `ALL_ANNOTATIONS_USAGE`에는 객체 owner 컬럼이 없으므로 annotation 조회는 허용 목록에서 선택된 정확한 `OBJECT_NAME`과 `OBJECT_TYPE='TABLE'`로 한정한다. DDL에 쓰이는 테이블·컬럼·annotation 이름은 호출 전에 대문자 식별자 규칙과 허용 테이블 목록으로 검증한다.
|
||||
|
||||
## 변경 파일과 책임
|
||||
|
||||
|
||||
@@ -20,10 +20,7 @@ public interface SchemaMetadataMapper {
|
||||
@Param("tableName") String tableName
|
||||
);
|
||||
|
||||
List<SchemaMetadataAnnotationRow> findAnnotations(
|
||||
@Param("owner") String owner,
|
||||
@Param("tableName") String tableName
|
||||
);
|
||||
List<SchemaMetadataAnnotationRow> findAnnotations(@Param("tableName") String tableName);
|
||||
|
||||
int countColumn(
|
||||
@Param("owner") String owner,
|
||||
@@ -32,13 +29,11 @@ public interface SchemaMetadataMapper {
|
||||
);
|
||||
|
||||
int countTableAnnotation(
|
||||
@Param("owner") String owner,
|
||||
@Param("tableName") String tableName,
|
||||
@Param("annotationName") String annotationName
|
||||
);
|
||||
|
||||
int countColumnAnnotation(
|
||||
@Param("owner") String owner,
|
||||
@Param("tableName") String tableName,
|
||||
@Param("columnName") String columnName,
|
||||
@Param("annotationName") String annotationName
|
||||
|
||||
@@ -145,7 +145,7 @@ public class SchemaMetadataService {
|
||||
|
||||
private Map<String, List<SchemaAnnotation>> annotationsByTarget(String tableName) {
|
||||
Map<String, LinkedHashMap<String, List<String>>> grouped = new LinkedHashMap<>();
|
||||
for (SchemaMetadataAnnotationRow row : mapper.findAnnotations(OWNER, tableName)) {
|
||||
for (SchemaMetadataAnnotationRow row : mapper.findAnnotations(tableName)) {
|
||||
String target = row.columnName() == null
|
||||
? tableTargetKey()
|
||||
: columnTargetKey(row.columnName());
|
||||
@@ -169,8 +169,8 @@ public class SchemaMetadataService {
|
||||
|
||||
private boolean annotationExists(String tableName, String columnName, String annotationName) {
|
||||
return columnName == null
|
||||
? mapper.countTableAnnotation(OWNER, tableName, annotationName) > 0
|
||||
: mapper.countColumnAnnotation(OWNER, tableName, columnName, annotationName) > 0;
|
||||
? mapper.countTableAnnotation(tableName, annotationName) > 0
|
||||
: mapper.countColumnAnnotation(tableName, columnName, annotationName) > 0;
|
||||
}
|
||||
|
||||
private String requireColumn(String tableName, String columnName) {
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
annotation_name,
|
||||
annotation_value
|
||||
FROM all_annotations_usage
|
||||
WHERE object_owner = #{owner,jdbcType=VARCHAR}
|
||||
AND object_name = #{tableName,jdbcType=VARCHAR}
|
||||
WHERE object_name = #{tableName,jdbcType=VARCHAR}
|
||||
AND object_type = 'TABLE'
|
||||
ORDER BY column_name NULLS FIRST, annotation_name, annotation_value
|
||||
</select>
|
||||
@@ -56,8 +55,7 @@
|
||||
<select id="countTableAnnotation" resultType="int">
|
||||
SELECT COUNT(*)
|
||||
FROM all_annotations_usage
|
||||
WHERE object_owner = #{owner,jdbcType=VARCHAR}
|
||||
AND object_name = #{tableName,jdbcType=VARCHAR}
|
||||
WHERE object_name = #{tableName,jdbcType=VARCHAR}
|
||||
AND object_type = 'TABLE'
|
||||
AND annotation_name = #{annotationName,jdbcType=VARCHAR}
|
||||
AND column_name IS NULL
|
||||
@@ -66,8 +64,7 @@
|
||||
<select id="countColumnAnnotation" resultType="int">
|
||||
SELECT COUNT(*)
|
||||
FROM all_annotations_usage
|
||||
WHERE object_owner = #{owner,jdbcType=VARCHAR}
|
||||
AND object_name = #{tableName,jdbcType=VARCHAR}
|
||||
WHERE object_name = #{tableName,jdbcType=VARCHAR}
|
||||
AND object_type = 'TABLE'
|
||||
AND annotation_name = #{annotationName,jdbcType=VARCHAR}
|
||||
AND column_name = #{columnName,jdbcType=VARCHAR}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.cloudhandson.vpdbackoffice.mapper;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class SchemaMetadataMapperXmlTest {
|
||||
|
||||
@Test
|
||||
void annotationUsageQueriesUseOnlyColumnsProvidedByTheAllView() throws IOException {
|
||||
try (var input = getClass().getResourceAsStream("/mapper/SchemaMetadataMapper.xml")) {
|
||||
String mapperXml = new String(input.readAllBytes(), StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(mapperXml)
|
||||
.contains("FROM all_annotations_usage")
|
||||
.contains("object_name = #{tableName,jdbcType=VARCHAR}")
|
||||
.doesNotContain("object_owner");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class SchemaMetadataServiceTest {
|
||||
void readsOnlyTheSelectedSmilegateTableThroughMapper() {
|
||||
when(mapper.findTableComment("SGMP_POC", "CZN_COMN_USER_MST"))
|
||||
.thenReturn("게임별 사용자 기준 정보");
|
||||
when(mapper.findAnnotations("SGMP_POC", "CZN_COMN_USER_MST")).thenReturn(List.of(
|
||||
when(mapper.findAnnotations("CZN_COMN_USER_MST")).thenReturn(List.of(
|
||||
new SchemaMetadataAnnotationRow(null, "BUSINESS_TERM", "게임 사용자"),
|
||||
new SchemaMetadataAnnotationRow("GUID", "BUSINESS_TERM", "사용자 고유 식별자"),
|
||||
new SchemaMetadataAnnotationRow("GUID", "BUSINESS_TERM", "고객 계정 식별자")
|
||||
@@ -62,14 +62,14 @@ class SchemaMetadataServiceTest {
|
||||
.extracting(annotation -> annotation.value())
|
||||
.isEqualTo("사용자 고유 식별자\n--- duplicate annotation value ---\n고객 계정 식별자");
|
||||
});
|
||||
verify(mapper).findAnnotations("SGMP_POC", "CZN_COMN_USER_MST");
|
||||
verify(mapper).findAnnotations("CZN_COMN_USER_MST");
|
||||
verify(mapper).findColumns("SGMP_POC", "CZN_COMN_USER_MST");
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacesAnExistingColumnAnnotationUsingMapperDdl() {
|
||||
when(mapper.countColumn("SGMP_POC", "CZN_COMN_USER_MST", "GUID")).thenReturn(1);
|
||||
when(mapper.countColumnAnnotation("SGMP_POC", "CZN_COMN_USER_MST", "GUID", "BUSINESS_TERM"))
|
||||
when(mapper.countColumnAnnotation("CZN_COMN_USER_MST", "GUID", "BUSINESS_TERM"))
|
||||
.thenReturn(1);
|
||||
|
||||
service.updateColumnAnnotation("game-users", "guid", "business_term", "사용자 고유 식별자");
|
||||
@@ -81,7 +81,7 @@ class SchemaMetadataServiceTest {
|
||||
|
||||
@Test
|
||||
void clearsAnExistingTableAnnotationWithoutAddingAnEmptyValue() {
|
||||
when(mapper.countTableAnnotation("SGMP_POC", "CZN_COMN_USER_MST", "BUSINESS_TERM"))
|
||||
when(mapper.countTableAnnotation("CZN_COMN_USER_MST", "BUSINESS_TERM"))
|
||||
.thenReturn(1);
|
||||
|
||||
service.updateTableAnnotation("game-users", "business_term", " ");
|
||||
|
||||
Reference in New Issue
Block a user